plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

huangapple go评论59阅读模式
英文:

plotly drops grouped legend (by color, by symbol) when converted from ggplot

问题

I don't quite understand why the legend disappeared when I converted a plot made by ggplot to plotly using ggplotly. The plotly help page did not have any information. I don't think their examples even worked properly on that page. Any help is greatly appreciated!

Sample data

library(scales)
packageVersion("ggplot2")
#> [1] '3.4.0'
library(plotly)
packageVersion("plotly")
#> [1] '4.10.1'

data <- data.frame(
  stringsAsFactors = FALSE,
  Level = c("Fast","Fast","Fast","Fast",
            "Fast","Fast","Slow","Slow","Slow",
            "Slow","Slow","Slow"),
  Period = c("1Year","3Month","1Year","3Month",
             "1Year","3Month","1Year","3Month",
             "1Year","3Month","1Year","3Month"),
  X = c(0.002,0.002,0.1,0.1,0.9,0.9,
        0.002,0.002,0.1,0.1,0.9,0.9),
  Y = c(1.38,1.29,1.61,1.61,1.74,0.98,
        1.14,0.97,1.09,1.1,0.94,0.58)
)

ggplot2

plt <- ggplot(data = data,
             aes(x = X,
                 y = Y,
                 shape = Period,
                 color = Level)) +
  geom_point(alpha = 0.6, size = 3) +
  labs(x = " ",
       y = "Value") +
  scale_y_continuous(labels = number_format(accuracy = 0.1)) +
  guides(color = guide_legend(title = "Level", order = 1),
         shape = guide_legend(title = "Period", order = 2)) +
  theme(axis.text.x = element_text(angle = 90))
plt

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

Convert to plotly, legend disappeared

ggplotly(plt, height = 500) %>%
  layout(xaxis = list(autorange = "reversed"))

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

Edit

There was an issue with guides(). If I removed it, the legend in ggplotly showed up

plt2 <- ggplot(data = data,
             aes(x = X,
                 y = Y,
                 shape = Period,
                 color = Level)) +
  geom_point(alpha = 0.6, size = 3) +
  labs(x = " ",
       y = "Value") +
  scale_y_continuous(labels = number_format(accuracy = 0.1)) +
  theme(axis.text.x = element_text(angle = 90))
plt2

ggplotly(plt2, height = 500) %>%
  layout(
    xaxis = list(autorange = "reversed"),
    legend = list(
      title = list(text = '(Period, Level)'))
  )

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

英文:

I don't quite understand why the legend disappeared when I converted a plot made by ggplot to plotly using ggplotly. The plotly help page did not have any information. I don't think their examples even worked properly on that page.
Any help is greatly appreciated!

Sample data

library(scales)
packageVersion(&quot;ggplot2&quot;)
#&gt; [1] &#39;3.4.0&#39;
library(plotly)
packageVersion(&quot;plotly&quot;)
#&gt; [1] &#39;4.10.1&#39;

data &lt;- data.frame(
  stringsAsFactors = FALSE,
  Level = c(&quot;Fast&quot;,&quot;Fast&quot;,&quot;Fast&quot;,&quot;Fast&quot;,
            &quot;Fast&quot;,&quot;Fast&quot;,&quot;Slow&quot;,&quot;Slow&quot;,&quot;Slow&quot;,
            &quot;Slow&quot;,&quot;Slow&quot;,&quot;Slow&quot;),
  Period = c(&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;,
             &quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;,
             &quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;),
  X = c(0.002,0.002,0.1,0.1,0.9,0.9,
        0.002,0.002,0.1,0.1,0.9,0.9),
  Y = c(1.38,1.29,1.61,1.61,1.74,0.98,
        1.14,0.97,1.09,1.1,0.94,0.58)
)

ggplot2

plt &lt;- ggplot(data = data,
             aes(x = X,
                 y = Y,
                 shape = Period,
                 color = Level)) +
  geom_point(alpha = 0.6, size = 3) +
  labs(x = &quot; &quot;,
       y = &quot;Value&quot;) +
  scale_y_continuous(labels = number_format(accuracy = 0.1)) +
  guides(color = guide_legend(title = &quot;Level&quot;, order = 1),
         shape = guide_legend(title = &quot;Period&quot;, order = 2)) +
  theme(axis.text.x = element_text(angle = 90))
plt

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。<!-- -->

Convert to plotly, legend disappeared

ggplotly(plt, height = 500) %&gt;%
  layout(xaxis = list(autorange = &quot;reversed&quot;))

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。<!-- -->

Edit

There was an issue with guides(). If I removed it, the legend in ggplotly showed up

plt2 &lt;- ggplot(data = data,
             aes(x = X,
                 y = Y,
                 shape = Period,
                 color = Level)) +
  geom_point(alpha = 0.6, size = 3) +
  labs(x = &quot; &quot;,
       y = &quot;Value&quot;) +
  scale_y_continuous(labels = number_format(accuracy = 0.1)) +
  theme(axis.text.x = element_text(angle = 90))
plt2

ggplotly(plt2, height = 500) %&gt;%
  layout(
    xaxis = list(autorange = &quot;reversed&quot;),
    legend = list(
      title = list(text = &#39;(Period, Level)&#39;))
  )

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。<!-- -->

答案1

得分: 6

以下是代码部分的翻译:

这是使用基本的R {plotly} 的一种解决方法,根据 @Tung 的要求修改图例:

```R
library(scales)
library(ggplot2)
library(plotly)
library(data.table)

DT <- data.frame(
  stringsAsFactors = FALSE,
  Level = c("Fast","Fast","Fast","Fast",
            "Fast","Fast","Slow","Slow","Slow",
            "Slow","Slow","Slow"),
  Period = c("1Year","3Month","1Year","3Month",
             "1Year","3Month","1Year","3Month",
             "1Year","3Month","1Year","3Month"),
  X = c(0.002,0.002,0.1,0.1,0.9,0.9,
        0.002,0.002,0.1,0.1,0.9,0.9),
  Y = c(1.38,1.29,1.61,1.61,1.74,0.98,
        1.14,0.97,1.09,1.1,0.94,0.58)
)

setDT(DT)

LevelDT <- unique(DT, by = "Level")
PeriodDT <- unique(DT, by = "Period")
LevelDT[, Y := min(DT$Y)-1]
PeriodDT[, Y := min(DT$Y)-1]

plt2 <- ggplot(data = DT,
               aes(x = X,
                   y = Y,
                   shape = Period,
                   color = Level)) +
  geom_point(alpha = 0.6, size = 3) +
  labs(x = " ",
       y = "Value") +
  scale_y_continuous(labels = number_format(accuracy = 0.1)) +
  theme(axis.text.x = element_text(angle = 90))
plt2

markercolors <- hue_pal()(2)

ggplotly(plt2, height = 500) |
  layout(
    xaxis = list(autorange = "reversed"),
    legend = list(
      title = list(text = ''),
      itemclick = FALSE,
      itemdoubleclick = FALSE,
      groupclick = FALSE
    )
  ) |
  add_trace(
    data = LevelDT,
    x = ~ X,
    y = ~ Y,
    inherit = FALSE,
    type = "scatter",
    mode = "markers",
    marker = list(
      color = markercolors,
      size = 14,
      opacity = 0.6,
      symbol = "circle"
    ),
    name = ~ Level,
    legendgroup = "Level",
    legendgrouptitle = list(text = "Level")
  ) |
  add_trace(
    data = PeriodDT,
    x = ~ X,
    y = ~ Y,
    inherit = FALSE,
    type = "scatter",
    mode = "markers",
    marker = list(
      color = "darkgrey",
      size = 14,
      opacity = 0.6,
      symbol = c("circle", "triangle-up")
    ),
    name = ~Period,
    legendgroup = "Period",
    legendgrouptitle = list(text = "Period")
  ) |> style(showlegend = FALSE, traces = 1:4)

希望这对你有帮助。如果有任何其他问题,请随时告诉我。

英文:

After OPs Edit:

Here is a workaround using basic R {plotly} to modify the legend according to @Tung's requirements:

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

library(scales)
library(ggplot2)
library(plotly)
library(data.table)
DT &lt;- data.frame(
stringsAsFactors = FALSE,
Level = c(&quot;Fast&quot;,&quot;Fast&quot;,&quot;Fast&quot;,&quot;Fast&quot;,
&quot;Fast&quot;,&quot;Fast&quot;,&quot;Slow&quot;,&quot;Slow&quot;,&quot;Slow&quot;,
&quot;Slow&quot;,&quot;Slow&quot;,&quot;Slow&quot;),
Period = c(&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;,
&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;,
&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;),
X = c(0.002,0.002,0.1,0.1,0.9,0.9,
0.002,0.002,0.1,0.1,0.9,0.9),
Y = c(1.38,1.29,1.61,1.61,1.74,0.98,
1.14,0.97,1.09,1.1,0.94,0.58)
)
setDT(DT)
LevelDT &lt;- unique(DT, by = &quot;Level&quot;)
PeriodDT &lt;- unique(DT, by = &quot;Period&quot;)
LevelDT[, Y := min(DT$Y)-1]
PeriodDT[, Y := min(DT$Y)-1]
plt2 &lt;- ggplot(data = DT,
aes(x = X,
y = Y,
shape = Period,
color = Level)) +
geom_point(alpha = 0.6, size = 3) +
labs(x = &quot; &quot;,
y = &quot;Value&quot;) +
scale_y_continuous(labels = number_format(accuracy = 0.1)) +
theme(axis.text.x = element_text(angle = 90))
plt2
markercolors &lt;- hue_pal()(2)
ggplotly(plt2, height = 500) |&gt;
layout(
xaxis = list(autorange = &quot;reversed&quot;),
legend = list(
title = list(text = &#39;&#39;),
itemclick = FALSE,
itemdoubleclick = FALSE,
groupclick = FALSE
)
) |&gt;
add_trace(
data = LevelDT,
x = ~ X,
y = ~ Y,
inherit = FALSE,
type = &quot;scatter&quot;,
mode = &quot;markers&quot;,
marker = list(
color = markercolors,
size = 14,
opacity = 0.6,
symbol = &quot;circle&quot;
),
name = ~ Level,
legendgroup = &quot;Level&quot;,
legendgrouptitle = list(text = &quot;Level&quot;)
) |&gt;
add_trace(
data = PeriodDT,
x = ~ X,
y = ~ Y,
inherit = FALSE,
type = &quot;scatter&quot;,
mode = &quot;markers&quot;,
marker = list(
color = &quot;darkgrey&quot;,
size = 14,
opacity = 0.6,
symbol = c(&quot;circle&quot;, &quot;triangle-up&quot;)
),
name = ~Period,
legendgroup = &quot;Period&quot;,
legendgrouptitle = list(text = &quot;Period&quot;)
) |&gt; style(showlegend = FALSE, traces = 1:4)

PS: Here the related plotly.js GitHub issue can be found.


Original answer:

I'm not sure why they are set to FALSE in the first place, but setting showlegend = TRUE in layout() and style() (for the traces) brings back the legend:

library(scales)
library(ggplot2)
library(plotly)
data &lt;- data.frame(
stringsAsFactors = FALSE,
Level = c(&quot;Fast&quot;,&quot;Fast&quot;,&quot;Fast&quot;,&quot;Fast&quot;,
&quot;Fast&quot;,&quot;Fast&quot;,&quot;Slow&quot;,&quot;Slow&quot;,&quot;Slow&quot;,
&quot;Slow&quot;,&quot;Slow&quot;,&quot;Slow&quot;),
Period = c(&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;,
&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;,
&quot;1Year&quot;,&quot;3Month&quot;,&quot;1Year&quot;,&quot;3Month&quot;),
X = c(0.002,0.002,0.1,0.1,0.9,0.9,
0.002,0.002,0.1,0.1,0.9,0.9),
Y = c(1.38,1.29,1.61,1.61,1.74,0.98,
1.14,0.97,1.09,1.1,0.94,0.58)
)
# ggplot2
plt &lt;- ggplot(data = data,
aes(x = X,
y = Y,
shape = Period,
color = Level)) +
geom_point(alpha = 0.6, size = 3) +
labs(x = &quot; &quot;,
y = &quot;Value&quot;) +
scale_y_continuous(labels = number_format(accuracy = 0.1)) +
guides(color = guide_legend(title = &quot;Period&quot;, order = 1),
shape = guide_legend(title = &quot;&quot;, order = 2)) +
theme(axis.text.x = element_text(angle = 90))
plt
# Convert to plotly, legend disappeared
fig &lt;- ggplotly(plt, height = 500) %&gt;%
layout(showlegend = TRUE, xaxis = list(autorange = &quot;reversed&quot;)) %&gt;%
style(showlegend = TRUE)
fig

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

答案2

得分: 3

以下是翻译好的部分:

这个答案适用于 plotly 4.10.1 版本。我定义了两个函数:

  1. set_legend_names() 这个函数用于编辑由 ggplotly() 创建的 htmlwidget 的名称,在传递给 plotly.js 之前。
  2. set_legend_symbols() 这个函数附加一些 JavaScript 代码到 htmlwidget 对象,用于在 plotly.js 绘制后更改图例符号。
plt2  |&gt;
    ggplotly(height = 500)  |&gt;
    layout(xaxis = list(autorange = &quot;reversed&quot;)) |&gt;
    set_legend_names() |&gt;
    set_legend_symbols() 

函数定义:

  1. set_legend_names()
set_legend_names &lt;- function(p,
                             new_legend_names = c(
                                 &quot;Fast&quot;, &quot;Slow&quot;, &quot;One Year&quot;, &quot;Three Month&quot;
                             )) {

    # 更新图例名称并放入一个组中
    for (i in seq_along(p$x$data)) {
        p$x$data[[i]]$name &lt;- new_legend_names[i]
    }

    p$x$layout$legend$title &lt;- &quot;&quot;

    return(p)
}
  1. set_legend_symbols()
set_legend_symbols &lt;- function(p,
                               symbol_nums_change_color = c(3, 4),
                               new_color_string = &quot;rgb(105, 105, 105)&quot;,
                               symbols_num_change_shape = 3,
                               symbols_nums_target_shape = 1) {
    js_get_legend &lt;- htmltools::HTML(
        &#39;let legend = document.querySelector(&quot;.scrollbox&quot;);
        let symbols = legend.getElementsByClassName(&quot;legendsymbols&quot;);
        const re = new RegExp(&quot;fill: rgb.+;&quot;, &quot;i&quot;);\n
        &#39;
    )

    js_symbol_const &lt;- paste0(
        &#39;const shape_re = new RegExp(\&#39;d=&quot;.*?&quot;\&#39;);\n&#39;,
        &quot;const correct_shape = symbols[&quot;,
        symbols_nums_target_shape,
        &quot;].innerHTML.match(shape_re)[0];\n&quot;
    )

    # 减去1以进行0索引的 JavaScript
    change_symbol_color_code &lt;- lapply(
        symbol_nums_change_color - 1,
        \(i)
        paste0(
            &quot;symbols[&quot;, i, &quot;].innerHTML = &quot;,
            &quot;symbols[&quot;, i, &quot;].innerHTML.replace(re,&quot;,
            &#39; &quot;fill: &#39;, new_color_string, &#39;;&quot;);&#39;
        )
    ) |&gt;
        paste(collapse = &quot;\n&quot;)

    # 减去1以进行0索引的 JavaScript
    change_symbols_shape_code &lt;- lapply(
        symbols_num_change_shape - 1,
        \(i)
        paste0(
            &quot;symbols[&quot;, i, &quot;].innerHTML = symbols[&quot;,
            symbols_nums_target_shape, &quot;].innerHTML.replace(shape_re, correct_shape);&quot;
        )
    ) |&gt;
        paste(collapse = &quot;\n&quot;)


    all_js &lt;- htmltools::HTML(
        unlist(c(
            js_get_legend,
            js_symbol_const,
            change_symbols_shape_code,
            change_symbol_color_code
        ))
    )

    # 将其添加到绘图中
    p &lt;- htmlwidgets::prependContent(
        p,
        htmlwidgets::onStaticRenderComplete(all_js)
    )

    return(p)
}

我以前从未发布过第二个答案,但在 plotly 4.10.1 中似乎有明显的不同之处。我迫不及待地期待 plotly 4.10.2 的发布,以便发布第三个答案。

英文:

This answer is for plotly 4.10.1. I have defined two functions:

  1. set_legend_names() This edits the names of the htmlwidget created by ggplotly(), before it is passed to plotly.js.
  2. set_legend_symbols(). This appends some js to the htmlwidget object to change the symbols after plotly.js has drawn them.
plt2  |&gt;
    ggplotly(height = 500)  |&gt;
    layout(xaxis = list(autorange = &quot;reversed&quot;)) |&gt;
    set_legend_names() |&gt;
    set_legend_symbols() 

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

Function definitions:

1. set_legend_names()

set_legend_names &lt;- function(p,
                             new_legend_names = c(
                                 &quot;Fast&quot;, &quot;Slow&quot;, &quot;One Year&quot;, &quot;Three Month&quot;
                             )) {

    # Update legend names and put in one group
    for (i in seq_along(p$x$data)) {
        p$x$data[[i]]$name &lt;- new_legend_names[i]
    }

    p$x$layout$legend$title &lt;- &quot;&quot;

    return(p)
}

2. set_legend_symbols()

set_legend_symbols &lt;- function(p,
                               symbol_nums_change_color = c(3, 4),
                               new_color_string = &quot;rgb(105, 105, 105)&quot;,
                               symbols_num_change_shape = 3,
                               symbols_nums_target_shape = 1) {
    js_get_legend &lt;- htmltools::HTML(
        &#39;let legend = document.querySelector(&quot;.scrollbox&quot;);
        let symbols = legend.getElementsByClassName(&quot;legendsymbols&quot;);
        const re = new RegExp(&quot;fill: rgb.+;&quot;, &quot;i&quot;);\n
        &#39;
    )

    js_symbol_const &lt;- paste0(
        &#39;const shape_re = new RegExp(\&#39;d=&quot;.*?&quot;\&#39;);\n&#39;,
        &quot;const correct_shape = symbols[&quot;,
        symbols_nums_target_shape,
        &quot;].innerHTML.match(shape_re)[0];\n&quot;
    )

    # subtract 1 for 0-indexed js
    change_symbol_color_code &lt;- lapply(
        symbol_nums_change_color - 1,
        \(i)
        paste0(
            &quot;symbols[&quot;, i, &quot;].innerHTML = &quot;,
            &quot;symbols[&quot;, i, &quot;].innerHTML.replace(re,&quot;,
            &#39; &quot;fill: &#39;, new_color_string, &#39;;&quot;);&#39;
        )
    ) |&gt;
        paste(collapse = &quot;\n&quot;)

    # subtract 1 for 0-indexed js
    change_symbols_shape_code &lt;- lapply(
        symbols_num_change_shape - 1,
        \(i)
        paste0(
            &quot;symbols[&quot;, i, &quot;].innerHTML = symbols[&quot;,
            symbols_nums_target_shape, &quot;].innerHTML.replace(shape_re, correct_shape);&quot;
        )
    ) |&gt;
        paste(collapse = &quot;\n&quot;)


    all_js &lt;- htmltools::HTML(
        unlist(c(
            js_get_legend,
            js_symbol_const,
            change_symbols_shape_code,
            change_symbol_color_code
        ))
    )

    # Add it to the plot
    p &lt;- htmlwidgets::prependContent(
        p,
        htmlwidgets::onStaticRenderComplete(all_js)
    )

    return(p)
}

I've never posted a second answer before but it seems substantially different in plotly 4.10.1. I eagerly anticipate the release of plotly 4.10.2 so I can post a third answer.

答案3

得分: 2

Plotly生成的图例与ggplot2不同 - 可以使用R和一点JavaScript来修复

首先要做的是确保您拥有相对较新的软件包版本:

packageVersion("ggplot2") # 3.4.0
packageVersion("plotly") # 4.10.0

有了这些版本,就像@Quentin一样,我确实获得了一个图例,尽管它与ggplot2生成的图例不同。

ggplotly(plt, height = 500) %>%
    layout(xaxis = list(autorange = "reversed"))

复制ggplot2图例的步骤:

  1. 更改图例文本。这可以通过在将其传递给plotly.js之前编辑R对象来完成。
  2. 删除shape指南中的颜色。这只能在绘图渲染后使用JavaScript完成。
  3. 将第三个圆形更改为三角形。这也需要在JavaScript中完成。

更改图例文本

要手动完成这一步,我们可以执行 p$x$data[[1]]$name <- "Fast",并为每个图层重复此操作。

幸运的是,您已手动指定了图例顺序,因此在传递给plotly之前知道在哪里访问正确的图例名称,如果只完成这一步,它将创建一个看起来像这样的图例,即仍然是错误的(第一个三角形应该是一个圆形,两者都不应该有颜色)。

更改符号形状和颜色

我们无法在R中完成这个操作。我已经编写了一个R辅助函数,生成一些JavaScript代码来完成这个操作:

get_symbol_change_js <- function(symbol_nums,
                                 new_color_string = "rgb(105, 105, 105)") {
    js_get_legend <- htmltools::HTML(
        'let legend = document.querySelector(".scrollbox");
        let symbols = legend.getElementsByClassName("legendsymbols");
        const re = new RegExp("fill: rgb.+;", "i");
        '
    )

    change_symbol_color_code <- lapply(
        symbol_nums,
        \(i)
        paste0(
            "symbols[", i, "].innerHTML = ",
            "symbols[", i, "].innerHTML.replace(re,",
            ' "', new_color_string, '";);'
        )
    ) |>
        paste(collapse = "\n")

    # 要更改的形状
    shape_change_num <- symbol_nums[1]

    # 要替换的形状
    shape_change_from <- shape_change_num - 1

    change_symbols_shape_code <- paste0(
        'const shape_re = new RegExp(\'d=".*?"\');\n',
        "const correct_shape = symbols[", shape_change_from, "].innerHTML.match(shape_re)[0];\n",
        "symbols[2].innerHTML = symbols[", shape_change_num, "].innerHTML.replace(shape_re, correct_shape);"
    )

    all_js <- htmltools::HTML(
        unlist(c(
            js_get_legend,
            change_symbol_color_code,
            change_symbols_shape_code
        ))
    )
    return(all_js)
}

我们可以将所有这些内容放在一起以生成所需的绘图:

draw_plotly_with_legend(plt)

最终的draw_plotly_with_legend()函数

请注意,此函数调用了上面定义的 get_symbol_change_js()。它还使用 htmlwidgets::prependContent() 在渲染之前附加我们的自定义HTML到小部件。

draw_plotly_with_legend <- function(gg = plt,
                                    guide_types = c("colour", "shape")) {

    # 期间、级别
    legend_categories <- lapply(
        guide_types, \(x) rlang::quo_get_expr(plt$mapping[[x]])
    )

    new_legend_names <- lapply(legend_categories, \(category) {
        unique(data[[category]])
    }) |> setNames(guide_types)

    # 找出需要去除颜色的符号
    symbols_to_remove_color <- new_legend_names[
        names(new_legend_names) != "colour"
    ] |> unlist()

    new_legend_names <- unlist(new_legend_names)

    symbol_num_remove_color <- which(
        new_legend_names %in% symbols_to_remove_color
    )

    # 创建绘图
    p <- ggplotly(gg, height = 500) %>%
        layout(xaxis = list(autorange = "reversed"))

    # 显示图例
    p$x$layout$showlegend <- TRUE

    # 更新图例名称并放入一个组中
    for (i in seq_along(p$x$data)) {
        p$x$data[[i]]$name <- new_legend_names[i]
        p$x$data[[1]]$legendgroup <- "Grouped legend"
    }

    # 获取更改图例颜色的js代码

    # js是从0开始索引的
    js_symbol_nums <- symbol_num_remove_color - 1
    js_code <- get_symbol_change_js(js_symbol_nums)

    # 将其添加到绘图中
    p <- htmlwidgets::prependContent(
        p,
        htmlwidgets::onStaticRenderComplete(js_code)
    )

    return(p)
}
英文:

Plotly generates a different legend from ggplot2 - this can be fixed with R and and a little javascript

The first thing to do is ensure that you have a reasonably current version of the packages:

packageVersion(&quot;ggplot2&quot;) # 3.4.0
packageVersion(&quot;plotly&quot;) # 4.10.0

With these versions, like @Quentin, I do get a legend, although it is different to the one generated by ggplot2.

ggplotly(plt, height = 500) %&gt;%
    layout(xaxis = list(autorange = &quot;reversed&quot;))

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

Steps to replicate the ggplot2 legend:

  1. Change the legend text. This can be done by editing the R object before it is passed to plotly.js.
  2. Remove the color from the shape guide. This can only be done with javascript after the plot has rendered.
  3. Change the third circle into a triangle. This also needs to be done in javascript.

Changing the legend text

To do this manually, we could do p$x$data[[1]]$name &lt;- &quot;Fast&quot;, and replicate for each layer.

Fortunately, you have manually specified the legend order, making it easy to know where to access the correct legend names before passing to plotly. If we just do this step, it will create a legend which looks like this, i.e. still wrong (the first triangle should be a circle and neither should be have a color):

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

Changing the symbol shape and colors

We cannot do this in R. I have written an R helper function to generate some javascript to do this for us:

get_symbol_change_js &lt;- function(symbol_nums,
                                 new_color_string = &quot;rgb(105, 105, 105)&quot;) {
    js_get_legend &lt;- htmltools::HTML(
        &#39;let legend = document.querySelector(&quot;.scrollbox&quot;);
        let symbols = legend.getElementsByClassName(&quot;legendsymbols&quot;);
        const re = new RegExp(&quot;fill: rgb.+;&quot;, &quot;i&quot;);
        &#39;
    )

    change_symbol_color_code &lt;- lapply(
        symbol_nums,
        \(i)
        paste0(
            &quot;symbols[&quot;, i, &quot;].innerHTML = &quot;,
            &quot;symbols[&quot;, i, &quot;].innerHTML.replace(re,&quot;,
            &#39; &quot;fill: &#39;, new_color_string, &#39;;&quot;);&#39;
        )
    ) |&gt;
        paste(collapse = &quot;\n&quot;)


    # shape to change
    shape_change_num &lt;- symbol_nums[1]

    # shape to replace with
    shape_change_from &lt;- shape_change_num - 1

    change_symbols_shape_code &lt;- paste0(
        &#39;const shape_re = new RegExp(\&#39;d=&quot;.*?&quot;\&#39;);\n&#39;,
        &quot;const correct_shape = symbols[&quot;, shape_change_from, &quot;].innerHTML.match(shape_re)[0];\n&quot;,
        &quot;symbols[2].innerHTML = symbols[&quot;, shape_change_num, &quot;].innerHTML.replace(shape_re, correct_shape);&quot;
    )

    all_js &lt;- htmltools::HTML(
        unlist(c(
            js_get_legend,
            change_symbol_color_code,
            change_symbols_shape_code
        ))
    )
    return(all_js)
}

We can put this all together to generate the plot as desired:

draw_plotly_with_legend(plt)

plotly 在从 ggplot 转换时删除了分组图例(按颜色、按符号)。

Final draw_plotly_with_legend() function

Note this function calls get_symbol_change_js(), as defined above. It also uses htmlwidgets::prependContent() to attach our custom html to the widget before rendering.

draw_plotly_with_legend &lt;- function(gg = plt,
                                    guide_types = c(&quot;colour&quot;, &quot;shape&quot;)) {

    # Period, Level
    legend_categories &lt;- lapply(
        guide_types, \(x) rlang::quo_get_expr(plt$mapping[[x]])
    )

    new_legend_names &lt;- lapply(legend_categories, \(category) {
        unique(data[[category]])
    }) |&gt; setNames(guide_types)

    # Work out which symbols need to have color removed
    symbols_to_remove_color &lt;- new_legend_names[
        names(new_legend_names) != &quot;colour&quot;
    ] |&gt; unlist()

    new_legend_names &lt;- unlist(new_legend_names)

    symbol_num_remove_color &lt;- which(
        new_legend_names %in% symbols_to_remove_color
    )

    # Create plot
    p &lt;- ggplotly(gg, height = 500) %&gt;%
        layout(xaxis = list(autorange = &quot;reversed&quot;))

    # Show legend
    p$x$layout$showlegend &lt;- TRUE

    # Update legend names and put in one group
    for (i in seq_along(p$x$data)) {
        p$x$data[[i]]$name &lt;- new_legend_names[i]
        p$x$data[[1]]$legendgroup &lt;- &quot;Grouped legend&quot;
    }

    # Get the js code to change legend color

    # js is 0 indexed
    js_symbol_nums &lt;- symbol_num_remove_color - 1
    js_code &lt;- get_symbol_change_js(js_symbol_nums)

    # Add it to the plot
    p &lt;- htmlwidgets::prependContent(
        p,
        htmlwidgets::onStaticRenderComplete(js_code)
    )

    return(p)
}

huangapple
  • 本文由 发表于 2023年2月7日 03:22:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365694.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定