PAIR-code / facets

Visualizations for machine learning datasets
https://pair-code.github.io/facets/
Apache License 2.0
7.35k stars 887 forks source link

Unable to invoke a custom infoRenderer #100

Open SamuelLarkin opened 6 years ago

SamuelLarkin commented 6 years ago

Hi all, I'm trying to use a custom infoRenderer but even using defaultInfoRenderer in my html fails.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Results</title>
<script>
        window.addEventListener('DOMContentLoaded', function() {
            var link = document.createElement('link');
            link.rel = "import";
            //link.href = "https://raw.githubusercontent.com/PAIR-code/facets/master/facets-dist/facets-jupyter.html";
            link.href = "facets-jupyter.html";  // Use a local copy.
            link.onload = function() {
                var dive = document.createElement('facets-dive');
                dive.crossOrigin = "anonymous";
                dive.data = [ /* Populated with data here */];
                var presets = {
                "horizontalFacet": "predicted_string",
                "horizontalBuckets": 23,
                "verticalBuckets": 23,
                "colorBy": "gender",
                "verticalFacet": "expected_string",
                //"infoRenderer": "FacetsDiveInfoCard.audioRenderer",   // Error when using my custom infoRenderer
                //"infoRenderer": "FacetsDiveInfoCard.defaultInfoRenderer",   // Still fails using the defaultInfoRenderer
                //"infoRenderer": "defaultInfoRenderer",   // Even not qualified it fails
                "imageFieldName": "name"};
                for (var key in presets) {
                    if (presets.hasOwnProperty(key))
                        dive[key] = presets[key];
                }
                document.body.appendChild(dive);
            }
            document.head.appendChild(link);
        });
    </script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.24/webcomponents-lite.js"></script>
<style>body, html { height: 100%; margin: 0; padding: 0; width: 100%; }</style>
</head>
<body></body>
</html>

Notice that I tried my custom infoRenderer, a qualified defaultInfoRenderer & defaultInfoRenderer, all three failed with a javascript error message in the client's browser aka Chrome.

I must say that I'm new to bazel and typescript. It would be nice to have more detailed instructions on own to write a customInfoRenderer.

Thanks

jimbojw commented 6 years ago

Hi Samuel,

It looks like you're specifying a string value here:

"infoRenderer": "FacetsDiveInfoCard.audioRenderer",

Instead, remove the quotes and pass in the literal function value. You don't need that function to be a part of any particular object, it can be a free-floating function.

function myAudioRenderer(selectedObject, element) {
  // Render selectedObject into the HTML element.
}
// ...
"infoRenderer": myAudioRenderer,

Hope this helps!

SamuelLarkin commented 6 years ago

Thanks for the quick response!

I should've RTFM more carefully ;) as it is clear that the infoRenderer is not a string.

I wrote a new solution where in the "demo".html I defined a function like you suggested. This seems to me to be a better solution than to recompile your library. Thus, I would suggest that your documentation on how to create a new infoRenderer be that solution.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Kaggle conv results</title>

<!--
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
-->
<script src="handlebars-v4.0.11.js"></script>

<script src="confusion_data.json"></script>

<script id="customInfoRenderer-template" type="text/x-handlebars-template">
  <div class="customInfoRenderer">
    <h1>Info Card</h1>
    <div class="body">
      <dl>
        <dt>expected</dt>
        <dd>{{expected}}</dd>
        <dt>expected_string</dt>
        <dd>{{expected_string}}</dd>
        <dt>filename</dt>
        <dd>{{filename}}</dd>
        <dt>predicted</dt>
        <dd>{{predicted}}</dd>
        <dt>predicted_string</dt>
        <dd>{{predicted_string}}</dd>
      </dl>
    </div>
    <audio controls autoplay>
      <source src="{{filename}}" type="audio/wav">
      Your browser does not support the audio element.
    </audio>
  </div>
</script>

<script id="customInfoRenderer" type="text/javascript">
  var source   = document.getElementById("customInfoRenderer-template").innerHTML;
  var template = Handlebars.compile(source);
  customInfoRenderer = function(selectedObject, elem) {
    elem.innerHTML = template(selectedObject);
  }
</script>

<script type="text/javascript">
   window.addEventListener('DOMContentLoaded', function() {
     var link = document.createElement('link');
     link.rel = "import";
     //link.href = "https://raw.githubusercontent.com/PAIR-code/facets/master/facets-dist/facets-jupyter.html";
     link.href = "facets-jupyter.html";
     link.onload = function() {
         var dive = document.createElement('facets-dive');
         dive.crossOrigin = "anonymous";
         dive.data = confusion_data;
         var presets = {
         "horizontalFacet": "predicted_string",
         "horizontalBuckets": 23,
         "verticalBuckets": 23,
         "colorBy": "gender",
         "verticalFacet": "expected_string",
         "infoRenderer": customInfoRenderer,
         "imageFieldName": "predicted_string"};
         for (var key in presets) {
             if (presets.hasOwnProperty(key))
                 dive[key] = presets[key];
         }
         document.body.appendChild(dive);
     }
     document.head.appendChild(link);
   });
</script>

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.24/webcomponents-lite.js"></script>

<style>body, html { height: 100%; margin: 0; padding: 0; width: 100%; }</style>
</head>
<body></body>
</html>

Obviously, using handlebars is not necessary.

SamuelLarkin commented 6 years ago

My answer was getting a bit long, so I decided to make two comments instead.

I was unable to get my other solution to work. Here are the modifications that I've done so far:

diff --git a/facets_dive/components/facets_dive_info_card/BUILD b/facets_dive/components/facets_dive_info_card/BUILD
index 5fd72a41b..e6e1dab82 100644
--- a/facets_dive/components/facets_dive_info_card/BUILD
+++ b/facets_dive/components/facets_dive_info_card/BUILD
@@ -14,6 +14,7 @@ ts_web_library(
     path = "/facets-dive/components/facets-dive-info-card",
     deps = [
         "//facets_dive/lib:info-renderers",
+        "//facets_dive/lib:audio-renderers",
         "@org_polymer_paper_card",
         "@org_polymer_paper_styles",
         "@org_tensorflow_tensorboard//tensorboard/components/tf_imports:polymer",
diff --git a/facets_dive/components/facets_dive_info_card/facets-dive-info-card.html b/facets_dive/components/facets_dive_info_card/
index a21373162..6438b532e 100644
--- a/facets_dive/components/facets_dive_info_card/facets-dive-info-card.html
+++ b/facets_dive/components/facets_dive_info_card/facets-dive-info-card.html
@@ -18,6 +18,7 @@ limitations under the License.
 <link rel="import" href="../../../paper-card/paper-card.html">
 <link rel="import" href="../../../paper-styles/typography.html">
 <link rel="import" href="../../lib/info-renderers.html">
+<link rel="import" href="../../lib/audio-renderers.html">

 <dom-module id='facets-dive-info-card'>
   <template>
diff --git a/facets_dive/components/facets_dive_info_card/facets-dive-info-card.ts b/facets_dive/components/facets_dive_info_card/fa
index a1c4772b4..bb4171a91 100644
--- a/facets_dive/components/facets_dive_info_card/facets-dive-info-card.ts
+++ b/facets_dive/components/facets_dive_info_card/facets-dive-info-card.ts
@@ -16,6 +16,7 @@
  */

 import {defaultInfoRenderer} from '../../lib/info-renderers';
+import {audioRenderer} from '../../lib/audio-renderers';

 export interface FacetsDiveInfoCard extends Element {
   /**
diff --git a/facets_dive/lib/BUILD b/facets_dive/lib/BUILD
index 13a05be86..04e12fa59 100644
--- a/facets_dive/lib/BUILD
+++ b/facets_dive/lib/BUILD
@@ -55,6 +55,15 @@ ts_web_library(
 )

 ts_web_library(
+    name = "audio-renderers",
+    srcs = [
+        "audio-renderers.html",
+        "audio-renderers.ts",
+    ],
+    path = "/facets-dive/lib",
+)
+
+ts_web_library(
     name = "label",
     srcs = [
         "label.html",
diff --git a/facets_dive/lib/audio-renderers.html b/facets_dive/lib/audio-renderers.html
new file mode 100644
index 000000000..b44c93812
--- /dev/null
+++ b/facets_dive/lib/audio-renderers.html
@@ -0,0 +1,17 @@
+<!--
+@license
+Copyright 2017 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<script src="audio-renderers.js"></script>
diff --git a/facets_dive/lib/audio-renderers.ts b/facets_dive/lib/audio-renderers.ts
new file mode 100644
index 000000000..ecdf2e59d
--- /dev/null
+++ b/facets_dive/lib/audio-renderers.ts
@@ -0,0 +1,45 @@
+/**
+ * @license
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * @fileoverview Example implementation of a custom renderer for the
+ * <facets-dive-info-card> component.
+ */
+
+/**
+ * Given an object and a container element to fill, render the data object's
+ * important information to the element.
+ */
+export const audioRenderer = (selectedObject: any, elem: Element) => {
+  const dl = document.createElement('dl');
+  for (const field in selectedObject) {
+    if (!selectedObject.hasOwnProperty(field)) {
+      continue;
+    }
+    const dt = document.createElement('dt');
+    dt.textContent = field;
+    dl.appendChild(dt);
+    const dd = document.createElement('dd');
+    dd.textContent = selectedObject[field];
+    dl.appendChild(dd);
+  }
+  elem.appendChild(dl);
+  const a = document.createElement('audio');
+  a.src = selectedObject['filename'];
+  a.controls = true;
+  a.autoplay = true;
+  elem.appendChild(a);
+};

Okay, not super creative by I simply wanted to perform the quickest hack possible to be able to play sound files.

Then I compiled your library using the following command and got no error. bazel build facets:facets_jupyter

Looking at bazel-genfiles/facets/facets-jupyter.html, it seems that my audio-renderer is empty and I don't know why that is the case.

<script>//~~WEBPATH~~/facets-dive/lib/info-renderers.js
function oh(a,b){var c=document.createElement("dl"),d;for(d in a)if(a.hasOwnProperty(d)){var e=document.createElement("dt");e.textContent=d;c.appendChild(e);e=document.createElement("dd");e.textContent=a[d];c.appendChild(e)}b.appendChild(c);c=document.createElement("audio");c.src=a.filename;c.controls=!0;c.autoplay=!0;b.appendChild(c)};
</script>

<script>//~~WEBPATH~~/facets-dive/lib/audio-renderers.js
</script>
jimbojw commented 6 years ago

Looking at bazel-genfiles/facets/facets-jupyter.html, it seems that my audio-renderer is empty and I don't know why that is the case.

The oh() function in the info-renderers.js file appears to be the compiled version of your audioRenderers() method. I'm not sure why the build system left audio-renderers.js empty, but it looks like the function has been processed.