Save/Load rpivottable 配置

Save/Load rpivottable configuration

我在几个 (rmarkdown) 网页上使用 rpivottable。

我已经看到 an example here 的 saving/restoring table 配置 to/from cookie。 由于我不擅长 javascript,我想问一下是否可以在 rmd 页面中以编程方式在 table 控件之上添加两个按钮,从而允许用户save/load 他们的首选 table 配置(cookie 或本地文件,如果可能)。您能否提供实现该目的的示例代码?

谢谢。

这个花了点时间。我使用本地存储。我这里有很多样式,但是没必要。我使用了 flexdashboard 的输出,因为这往往会给我带来最多的 JS 问题。

<style>
body {    /*push content away from far right and left edges*/
  margin-right: 2%;
  margin-left: 2%;
}
.rpivotTable {
  overflow:auto;
  resize: both;
  box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
  -moz-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
  -webkit-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
  border: 1px solid white;
  padding: 5px;
  margin: 5px 20px 50px 5px;
}
.btn {
  vertical-align: middle;
  -moz-box-shadow: 0px 10px 14px -7px #000000;
  -webkit-box-shadow: 0px 10px 14px -7px #000000;
  box-shadow: 0px 10px 14px -7px #000000;
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
  border: .5px solid black;
  display: inline-block;
  font-size: 1.3em; 
  padding: .3em 0px;
  width: 18em;
  text-decoration: none; /*no underline!!*/
  cursor: pointer;
}
.btn:active { /*simulate movement*/
  position: relative;
  top: 1px;
}
</style>

我已经使用了我在其他问题中找到的内容。

## R Markdown

<div style="margin-right:5%;">

`r stringi::stri_rand_lipsum(10)`

</div>

```{r cars}

library(rpivotTable)

data(mtcars)
names(mtcars)[10] <- "George.Dontas"

```

Here is the **first** Div.

## Including Plots

Do you want to save or restore the previously saved pivot tables' configuration?

<a id='saveBtn' class='btn' style="background-color:#003b70;color:white;">Save Current Configuration</a>
<a id='restoBtn' class='btn' style="background-color:#b21e29;color:white;">Restore Previous Configuration</a>

```{r pressure, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols=c("cyl","carb"),width="100%", height="400px")
```

```{r morePressure, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols=c("cyl","carb"),width="100%", height="400px")
```

This should be a different aspect of the report.

```{r evenMorePressure, echo=FALSE, fig.show="hold"}
rpivotTable(mtcars,rows="George.Dontas", cols=c("cyl","carb"),width="100%", height="400px")
```

这是JS/JQuery...它有点难看,而且是两者的大杂烩(JS/JQuery)。

```{r listenOrElse,results="as-is",engine="js"}

// save current state of the tables to my browser
setTimeout(function(){       //add the events first
  document.querySelector('a#saveBtn').addEventListener('click', savoring);
  document.querySelector('a#restoBtn').addEventListener('click', giveItBack);
  function savoring() {                             // function to save
    el = document.querySelectorAll('.rpivotTable');
    for(i=0; i < el.length; i++){
      elId = el[i].getAttribute("id");
      stringy = $('#' + elId).data("pivotUIOptions"); // collect rows/columns filters
      delete stringy['aggregators'];                 // remove the arbitrary
      delete stringy['renderers'];
      stringy2 = JSON.stringify(stringy);            // make it one key:value
      window.localStorage.setItem('table' + i, stringy2); // store it!
    }
  };
  function giveItBack() {                           // function to regurgitate
    el = document.querySelectorAll('.rpivotTable');
    console.log("working on the giver");
    ods = [...el[0].ownerDocument.scripts];         // make it an array
    for(j=0; j < el.length; j++){
      elId = el[j].getAttribute("id");
      where = ods.filter(function(ods){             // filter scripts for table data
        return ods.dataset['for'] === elId;
      })[0].innerHTML; 
      where2 = JSON.parse(where).x.data;            // WOOO HOO! I figured it out!!
      where3 = HTMLWidgets.dataframeToD3(where2);   // finally sheesh!!
      gimme = window.localStorage.getItem('table' + j); // get storage
      $('#' + elId).pivotUI(where3, JSON.parse(gimme), true, "en"); // put it back!
    }
  }
},100);

```



更新

感谢@George Dontas 指出一些改进的机会。此更新更改了配置的保存方式。不过,我相信仍然有改进的方法。

此更新将文件或网页名称添加为用于存储信息的 key-value 对的一部分。现在,webpage/script 和 table 号码的名称都需要匹配才能更新 table。此外,这将在无法恢复配置时提醒用户。如果没有保存任何内容并且没有保存文件名和 table 匹配配置,则会出现此警报。

保存配置的更新

savoring()新增一行代码,修改一行代码。

新:

path = window.location.pathname.split("/").pop().split(".").slice()[0]; //f name

修改:

window.localStorage.setItem(path + '_table' + i, stringy2); // store it

整个函数有变化:

  function savoring() {                     // function to save
    el = document.querySelectorAll('.rpivotTable');
    path = window.location.pathname.split("/").pop().split(".").slice()[0];
    for(i=0; i < el.length; i++){
      elId = el[i].getAttribute("id");
      stringy = $('#' + elId).data("pivotUIOptions"); // collect filters
      delete stringy['aggregators'];        // remove the arbitrary
      delete stringy['renderers'];
      stringy2 = JSON.stringify(stringy);   // make it one key:value
      window.localStorage.setItem(path + '_table' + i, stringy2);  // store it
    }
  };

恢复配置的更新

此函数中的新行很少。名称必须收集,如 savoring() 更改。此外,此功能现在为用户提供了警报。

I started out with the basic system alert, but it wasn't up to snuff for my tastes, so I also developed a custom alert box. I've included both here.

基本警报和更新配置检索

与我最初的基本警报答案相比,唯一不同的是 giveItBack() 函数中的以下代码行:

path = window.location.pathname.split("/").pop().split(".").slice()[0]; //f name

  if(window.localStorage.getItem(path + '_table' + j) === null) {
    jj = j + 1;
    alert("WARNING: There is no saved pivot table configuration for " + path + "'s table " + jj + ".");
    continue; // don't update, go to next table (if more than 1)
  }

这里是完整的giveItBack()函数(注意这里有notice(msg)msg,但是注释掉了):

function giveItBack() {               // function to regurgitate
    el = document.querySelectorAll('.rpivotTable');
    console.log("working on the giver");
    ods = [...el[0].ownerDocument.scripts];   // make it an array
    path = window.location.pathname.split("/").pop().split(".").slice()[0]; //name
    for(j=0; j < el.length; j++){
      elId = el[j].getAttribute("id");
      where = ods.filter(function(ods){     // filter scripts data
        return ods.dataset['for'] === elId;
      })[0].innerHTML; 
      where2 = JSON.parse(where).x.data;    // WOOO HOO! I figured it out!!
      where3 = HTMLWidgets.dataframeToD3(where2); // finally formatted
      // is there a saved configuration that matches this file and table?
      if(window.localStorage.getItem(path + '_table' + j) === null) {
        jj = j + 1;
                  //this is for the standard alert box
        alert("WARNING: There is no saved pivot table configuration for " + path + "'s table " + jj + ".");
        //msg = "<b>WARNING</b><br><br>There is no saved pivot table configuration for<br>" + path + "."
        //notice(msg); //this is for the custom alert box
        continue; // go to next loop
      }
      gimme = window.localStorage.getItem(path + '_table' + j); // get storage
      $('#' + elId).pivotUI(where3, JSON.parse(gimme), true, "en"); // put it back!
    }
  };

自定义警报和更新的配置检索

如果您选择对警报消息使用更自定义的方法,则还有很多(幸运的是,它应该是复制和粘贴)。您将使用基本警报更新中的 giveItBack 函数,但注释掉或删除 alert(... 并取消注释 msgnotice().

For the CSS in my original answer, update the styles for .btn to .btn, #noted and .btn:active to btn:active, #noted:active.

这是自定义提醒的剩余 CSS。您可以将此 CSS 添加到其他样式标签或将它们分开。

<style>
#notice-wrapper {
  width: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000000;
  background: transparent;
  display: none;
  transition: opacity 1s ease-in;
}
#notice-box {
  -moz-box-shadow: 0px 10px 14px -7px #000000;
  -webkit-box-shadow: 0px 10px 14px -7px #000000;
  box-shadow: 0px 10px 14px -7px #000000;
  border-radius: 4px;
  border: .5px solid black;
  width = 300px;
  background: #003b70;
  color: white;
  min-height: 200px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -100px 0 0 -150px;
}
#notHead {
  text-align: center;
  font-size: 1.3em;
  padding: 4px;
  margin: 2.5em;
  font-family: Verdana, sans-serif;
}
#noted {
  background: #b21e29;
  margin: .5em;
  width: 120px;
  font-family: Verdana, sans-serif;
}
</style>

接下来是自定义警告框的 JS。我把这个函数放在 setTimeout(function(){savoring()giveItBack() 中。

  function notice(msg) {
    function cr() {
      if(document.querySelector('#notice-wrapper') === null) {
          wrapper = document.createElement('div');
          wrapper.id = 'notice-wrapper';
          html = "<div id='notice-box'><h2 id='notHead'></h2><div id='noticeBtns'>";
          html += "<button id='noted'>OK</button></div></div>";
          wrapper.innerHTML = html;
          document.body.appendChild(wrapper);
      }
      insta = document.querySelector('#notice-wrapper');
      placer(insta);
      return(insta);
    }
    function placer(insta) {
      wrapper = insta;
      winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientheight;
      wrapper.style.height = winHeight + "px";
    }
    function showy(el) {
      el.style.display = "block";
      el.style.opacity = 1;
    }
    function goAway(el) {
      el.style.opacity = 0;
      setTimeout(function(){
        el.style.display = "none";
      }, 1000);
    }
    function takeAction(msg) {
      insta = cr();
      insta.querySelector('#notHead').innerHTML = msg;
      showy(insta);
      insta.querySelector('#noted').addEventListener('click', function() {
        goAway(insta);
      }, false);
    }
    takeAction(msg);
  }

当然,使用此自定义选项,您可以根据自己的喜好设置样式。样式控制不是系统警报消息系统的选项。

不要标记这个答案——它不是另一个答案——它是一个更新。 (并且写给任何可能阅读它的人;而不仅仅是问...的人)

这种保存和恢复table配置的更新方法是基于包rpivotTable的修改版本(在Github中:fraupflaume/rpivotTable)。

I've kept this answer self-contained so that you would not have to look at the other answer to assemble the code.

  • 这适用于同一 RMarkdown 呈现网页中的一对多 rpivotTable 小部件。
  • Dynamic Sizing: 此脚本包含动态调整大小,受网页可用宽度限制(不会与其他内容重叠,不会 使网页变宽)。为了动态调整大小——
    • 如果使用输出 html_document,则必须取消设置 class main-container 的 max-width。此设置会产生巨大的边距,对于此小部件来说 anti-conducive。
    • 您需要使用 overflow: auto 设置 class .rpivotTable 的样式;您不能将 resize 分配给此 class。
    • 如果使用输出 flex_dashboard,则不能保留默认设置 vertical-scroll: fit(与动态调整大小相反!)
    • 您不能在小部件级别设置小部件的默认高度和宽度。设置此项会使大小 完全 静态。
    • 完全可以接受table在创建table(rpivotTable())时将table的高度和宽度设置为静态或动态尺寸。
    • 我忘记了什么?
  • Cookie-ish Alert:配置保存有两个选项用于呈现警报(表示没有数据要恢复的警报)。
    • 系统警报:如果使用系统警报,cookie 函数中有两行Javascript。与警报相关的 Javascript 和 CSS 的其余部分用于自定义警报选项。
    • Custom Alert:我将从两个角度来讨论这个问题:CSS 和 JS。
      • CSS:唯一需要 CSS 确保 pop-up 警报确认按钮具有 cursor: pointer 样式。我 相信 超出此范围的样式是严格的偏好。与警报相关的 CSS 包括以下 classes 和 id:#noted#notice-wrappernotice-boxnotHead
      • JS:cookie-ish函数中只有几行JS,giveItBack。警告框的 notice 函数。
  • Cookie-ish 与 subtotal:此问题的修复(以及 tsv 可能存在的问题)需要cookie-ish 函数 giveItBack 中的几行额外代码。 savoring没变。
  • Cookie-ish对齐方式:目前配置是通过文件名或网页名和迭代来标识的(当你有多个table,它们按索引出现的顺序)。考虑重新排序 table 或重命名 file/page.
  • 的影响

动态大小块

这个块可以放在你脚本的任何地方;假设:echo=FALSE 是默认值,或者输出选项隐藏回声。

```{r spicy, engine="js", include=FALSE, results="asis"}

scrp = ["https://cdnjs.cloudflare.com/ajax/libs/css-element-queries/1.2.3/ElementQueries.min.js",
        "https://cdnjs.cloudflare.com/ajax/libs/css-element-queries/1.2.3/ResizeSensor.js"];

setTimeout(function(){ // this function adds the URLs to the HTML <head>
  for(i=0; i < scrp.length; i++) {
    script = document.createElement('script');
    script.src = scrp[i];
    script.type = "text/javascript";
    document.head.appendChild(script);
  }
  ElementQueries.listen(); // just listen!!
}, 200); //wait a sec!
```

Cookie-ish 和警报

的 JS

应该有很多评论来解读其作用的基本思想。

```{r listenOrElse,results="asis",engine="js"}

// save current state of the tables to my browser
setTimeout(function(){
                       // add to buttons
  document.querySelector('a#saveBtn').addEventListener('click', savoring); 
  document.querySelector('a#restoBtn').addEventListener('click', giveItBack);
  function savoring() {                             // function to save
    el = document.querySelectorAll('.rpivotTable');
    path = window.location.pathname.split("/").pop().split(".").slice()[0]; //filename
    for(i=0; i < el.length; i++){
      elId = el[i].getAttribute("id");
      stringy = $('#' + elId).data("pivotUIOptions");  // collect rows/col filters
      delete stringy['aggregators'];                   // remove not-parse-friendly keys
      delete stringy['renderers'];
      stringy2 = JSON.stringify(stringy);              // one key:value pair for storage
      window.localStorage.setItem(path + '_table' + i, stringy2);  // STORE it!
    }
  };
  function giveItBack() {                           // function to regurgitate
    el = document.querySelectorAll('.rpivotTable');
    console.log("working on the giver");
    ods = [...el[0].ownerDocument.scripts];         // make it an array
    path = window.location.pathname.split("/").pop().split(".").slice()[0]; //filename
    for(j=0; j < el.length; j++){
      elId = el[j].getAttribute("id");
      where = ods.filter(function(ods){             // filter scripts for data
        return ods.dataset['for'] === elId;
      })[0].innerHTML; 
      where2 = JSON.parse(where).x.data;            // format data for pivotUI()
      where3 = HTMLWidgets.dataframeToD3(where2);   // ...still formatting
      if(window.localStorage.getItem(path + '_table' + j) === null) { // alert
    // basic system alert
        //jj = j + 1;                                                 
        //alert("WARNING: There is no saved pivot table configuration for " + path + "'s table " + jj + ".");
    // custom alert
        msg = "<b>WARNING</b><br><br>There is no saved pivot table configuration for<br>" + path + "."
        notice(msg);
    // Either Alert: from here on needed whether basic or custom alert
        continue;
      }
      gimme = window.localStorage.getItem(path + '_table' + j); // get storage
      gimmeMore = JSON.parse(gimme);                            // prepare for recall
      if(where.includes('"subtotals":true')){       // is the option 'subtotals' used?
        gimmeMore.renderers = $.pivotUtilities.subtotal_renderers;
        gimmeMore.dataClass = $.pivotUtilities.SubtotalPivotData;
      }; 
      if(where.includes('"tsv":true')){             // is the option 'tsv' used?
        gimmeMore.renderers = $.extend(gimmeMore.renderers, $.pivotUtilities.export_renderers);
      };
      $('#' + elId).pivotUI(where3, gimmeMore, true, "en"); // put it back!
    }
  };
  function notice(msg) { // all of this is for the custom alert box 
    function cr() {
      if(document.querySelector('#notice-wrapper') === null) { // if an alert doesn't exist
          wrapper = document.createElement('div');
          wrapper.id = 'notice-wrapper';
          html = "<div id='notice-box'><h2 id='notHead'></h2><div id='noticeBtns'>";
          html += "<button id='noted'>OK</button></div></div>";
          wrapper.innerHTML = html;
          document.body.appendChild(wrapper);
      }
      insta = document.querySelector('#notice-wrapper'); // a container for the alert box 
      placer(insta);
      return(insta);
    }
    function placer(insta) { // make the size reasonable, based on viewing screen
      wrapper = insta;
      winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientheight;
      wrapper.style.height = winHeight + "px";
    }
    function showy(el) {
      el.style.display = "block";  // keep the content confined; add a control
      el.style.opacity = 1;
    }
    function goAway(el) {          // use the control to hide the alert when ack
      el.style.opacity = 0;
      setTimeout(function(){
        el.style.display = "none";
      }, 1000);
    }
    function takeAction(msg) { // use all of the above: make alert and render it
      insta = cr();
      insta.querySelector('#notHead').innerHTML = msg;
      showy(insta);
      insta.querySelector('#noted').addEventListener('click', function() {
        goAway(insta);
      }, false);
    }
    takeAction(msg); // pop-up ENGAGED
  }
},200); // give me a sec—my browser may be slow... or my widgets may be fast... or...

```

CSS

这不会进入 R 块。这是我示例中使用的所有 CSS。我没有使用外部样式 sheet,尽管这是更好的做法。

此 CSS 控制警报、用于保存和检索配置的按钮,以及正文和边距样式。我包含了 CSS 以使枢轴 tables 弹出,但它被注释掉了(这些是 /* CSS commenters */)。您可以use/not自行决定使用。

<style>
body {    /*push content away from far right and left edges*/
  margin-right: 2%;
  margin-left: 2%;
}
.main-container {
  max-width: unset; // remove default from RMD
}
.rpivotTable {
  overflow:auto; /*this is absolutely needed*/
  /*resize: both; <- cannot have this*/
  /*box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
  -moz-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
  -webkit-box-shadow: 0 22px 70px 4px rgba(0,0,0,0.56);
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
  border: 1px solid white;
  padding: 5px;
  margin: 5px 20px 50px 5px;
  max-width: 1100px;*/
}
.btn, #noted {
  vertical-align: middle;
  -moz-box-shadow: 0px 10px 14px -7px #000000;
  -webkit-box-shadow: 0px 10px 14px -7px #000000;
  box-shadow: 0px 10px 14px -7px #000000;
  -moz-border-radius: 4px;
  -webkit-border-radius: 4px;
  border-radius: 4px;
  border: .5px solid black;
  display: inline-block;
  font-size: 1.3em; 
  padding: .3em 0px;
  width: 18em;
  text-decoration: none; /*no underline!!*/
  cursor: pointer;
}
.btn:active, #noted:active { /*simulate movement*/
  position: relative;
  top: 1px;
}
#notice-wrapper {
  width: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000000;
  background: transparent;
  display: none;
  transition: opacity 1s ease-in;
}
#notice-box {
  -moz-box-shadow: 0px 10px 14px -7px #000000;
  -webkit-box-shadow: 0px 10px 14px -7px #000000;
  box-shadow: 0px 10px 14px -7px #000000;
  border-radius: 4px;
  border: .5px solid black;
  width = 300px;
  background: #003b70;
  color: white;
  min-height: 200px;
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -100px 0 0 -150px;
}
#notHead {
  text-align: center;
  font-size: 1.3em;
  padding: 4px;
  margin: 2.5em;
  font-family: Verdana, sans-serif;
}
#noted {
  background: #b21e29;
  margin: .5em;
  width: 120px;
  font-family: Verdana, sans-serif;
}
</style>

R <- 那也是...嗯,剩下的RMD...

这是我用作示例的 RMD 的代码。我唯一没有包括的是我的版本说明(现在是 16...)。

---
title: "rpivottable_test"
output: html_document
---

```{r setup,include=F}
knitr::opts_chunk$set(echo = FALSE)
```

```{r data,include=F}
# devtools::install_github("fraupflaume/rpivotTable")
library(rpivotTable)
data(mtcars)
names(mtcars)[10] <- "George.Dontas"
```

## Make it Interesting...or not

Do you want to save or restore the previously saved pivot tables' configuration?

<!--- cookie-ish's buttons --->
<a id='saveBtn' class='btn' style="background-color:#003b70;color:white;">Save Current Configuration</a>
<a id='restoBtn' class='btn' style="background-color:#b21e29;color:white;">Restore Previous Configuration</a>

```{r showMe, echo=FALSE, fig.show="hold"}

rpivotTable(mtcars,rows="George.Dontas", cols = c("cyl"), width = "90%", height = "40%",
            rendererOptions = list(
              c3 = list(legend = list(show = FALSE), 
                        data = list(labels = TRUE),
                        options = list(responsive = TRUE,
                                       maintainAspectRatio = FALSE),
                        size = list(width = "600",
                                    height = "500")),
              d3 = list(size = list(width = "500", height = "500")))) 
```

`r stringi::stri_rand_lipsum(3)`

## How about Another Table?

Tell me things. Make sure I am not going to overlap later. You better be listening!

```{r morePressure, echo=FALSE, fig.show="hold"}

rp <- rpivotTable(mtcars, rows = c("mpg", "am"), cols = "cyl", 
                  width = "90%", height = "40%", subtotals = T,
                  tsv = T,
                  rendererOptions = list(
                    c3 = list(legend = list(show = FALSE), # this works!!
                              data = list(labels = TRUE),
                              size = list(width = "600",
                                          height = "500"),
                              options = list(responsive = TRUE,
                                             maintainAspectRatio = FALSE))))
rp
```

This should be *anywhere* other than here.

```{r itsMine, echo=FALSE, fig.show="hold"}

df1 <- data.frame(where = LETTERS[1:3], what = c(3.6, 5.6, 1.1))
x = rpivotTable(df1, width="80%", height="40%", 
                aggregatorName = "Count", 
                vals = "Sum", 
                cols = "where",   
                rows = "what", 
                rendererOptions = list(c3 = list(legend = list(show = FALSE), # this works!!
                                                 data = list(labels = TRUE),
                                                 size = list(width = "500",
                                                             height = "500"),
                                                 options = list(responsive = TRUE,
                                                                maintainAspectRatio = FALSE))))

x

```

Put something here

第一个table我只控制了d3大小。下图反映了控制和不受控制的树图大小调整之间的差异。

我在浏览器中进行了缩小,以便可以看到第二个树状图的限制:

虽然我试图提供足够的信息让任何遇到此问答的人都可以使用它,但我还是焦急地等待提问者找到破解它的方法:)