问:
How to make collapsing toolbar square for all devices?
I got the collapsing toolbar.  The image inside it has to be square but on devices, with big the screen, it is not. How can I set it up to let image be all the time square and make image full width and hight?

At the same time toolbar has to be collapsed it cant be fixed and frozen.

The code of activity.

```<code><?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:background="@android:color/holo_blue_bright"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/friend_activity_image"
                android:layout_width="match_parent"
                android:layout_height="220dp"
                android:layout_alignParentStart="true"
                android:layout_alignParentTop="true"
                android:adjustViewBounds="true"
                android:scaleType="centerCrop"
                android:contentDescription="Default Avatar"
                android:fitsSystemWindows="true"
                android:src="@drawable/avatar"
                app:layout_collapseMode="parallax" />

            <LinearLayout
                android:id="@+id/meta_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:alpha="0.8"
                android:background="@android:color/black"
                android:orientation="vertical"
                android:paddingBottom="16dp"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:paddingTop="16dp">

                <TextView
                    android:id="@+id/friend_activity_tv_name"
                    style="?android:attr/textAppearanceLarge"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-condensed"
                    android:paddingBottom="0dp"
                    android:text="NAME"
                    android:textColor="#fff"
                    android:textSize="24sp"
                    android:textStyle="bold" />

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/friend_activity_tv_age"
                        style="?android:attr/textAppearanceSmall"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="AGE"
                        android:textColor="#8fff"
                        android:textSize="16sp" />

                    <TextView
                        android:id="@+id/friend_activity_tv_country"
                        style="?android:attr/textAppearanceLarge"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:paddingBottom="0dp"
                        android:text="COUNTRY"
                        android:textColor="#8fff"
                        android:textSize="16sp" />
                </LinearLayout>

            </LinearLayout>

            <!--android:src="@drawable/default_avatar"-->
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView

        android:id="@+id/nested_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.CardView
                android:id="@+id/friend_activity_cardview_about"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp">

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recycler_list_friendDescription"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
                </android.support.v7.widget.RecyclerView>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:id="@+id/friend_activity_cardview_buttons_block"
                android:layout_below="@+id/friend_activity_cardview_about"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginTop="8dp">

                <LinearLayout
                    android:id="@+id/my_profile_linear_likers_visitors"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <Button
                        android:id="@+id/friend_activity_btn_add_friend"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:backgroundTint="@android:color/white"
                        android:paddingLeft="25dp"
                        android:paddingRight="25dp"
                        android:text="+ ADD FRIEND"
                        android:textColor="@color/colorPrimary" />

                    <Button
                        android:id="@+id/friend_activity_btn_start_chat"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:backgroundTint="@android:color/white"
                        android:paddingLeft="25dp"
                        android:paddingRight="25dp"
                        android:text="WRITE MESSAGE"
                        android:textColor="@color/colorPrimary" />

                </LinearLayout>
            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:id="@+id/friend_activity_cardview_list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/friend_activity_cardview_buttons_block"
                android:layout_marginBottom="8dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <Button
                        android:id="@+id/settings_image_btn"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:backgroundTint="@android:color/white"
                        android:paddingLeft="25dp"
                        android:paddingRight="25dp"
                        android:text="@string/check_your_search_parameters"
                        android:textColor="@color/colorPrimary" />

                    <TextView
                        android:id="@+id/my_profile_tv_also_like"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_below="@+id/settings_image_btn"
                        android:layout_gravity="left"
                        android:layout_marginLeft="8dp"
                        android:layout_marginTop="8dp"
                        android:text="@string/you_may_also_like_them"
                        android:textSize="24sp"
                        android:textStyle="bold" />

                    <android.support.v7.widget.RecyclerView
                        android:id="@+id/my_profile_recyler"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_below="@+id/my_profile_tv_also_like" />
                </LinearLayout>

            </android.support.v7.widget.CardView>

        </RelativeLayout>

    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/friend_activity_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@drawable/ic_favorite_black_24dp"
        app:fabSize="normal"
        app:layout_anchor="@id/app_bar_layout"
        app:layout_anchorGravity="bottom|end" />

</android.support.design.widget.CoordinatorLayout>
</code>```

答:
You have to programmatically set the height within onCreate

```<code>Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int width = size.x;

        AppBarLayout abl=findViewById(R.id.appbar);
        CoordinatorLayout.LayoutParams ap=(CoordinatorLayout.LayoutParams) abl.getLayoutParams();
        ap.height=width;

        abl.setLayoutParams(ap);
</code>```

in the resource xml, on AppBarLayout
you also have to set

```<code>android:fitsSystemWindows="false"
</code>```

otherwise the square will start from the very top (including status bar battery, notification etc.), unless you want the square to begin from there.

Body:
I got the collapsing toolbar.  The image inside it has to be square but on devices, with big the screen, it is not. How can I set it up to let image be all the time square and make image full width and hight?

At the same time toolbar has to be collapsed it cant be fixed and frozen.

The code of activity.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            android:background="@android:color/holo_blue_bright"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/friend_activity_image"
                android:layout_width="match_parent"
                android:layout_height="220dp"
                android:layout_alignParentStart="true"
                android:layout_alignParentTop="true"
                android:adjustViewBounds="true"
                android:scaleType="centerCrop"
                android:contentDescription="Default Avatar"
                android:fitsSystemWindows="true"
                android:src="@drawable/avatar"
                app:layout_collapseMode="parallax" />

            <LinearLayout
                android:id="@+id/meta_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:alpha="0.8"
                android:background="@android:color/black"
                android:orientation="vertical"
                android:paddingBottom="16dp"
                android:paddingLeft="16dp"
                android:paddingRight="16dp"
                android:paddingTop="16dp">

                <TextView
                    android:id="@+id/friend_activity_tv_name"
                    style="?android:attr/textAppearanceLarge"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:fontFamily="sans-serif-condensed"
                    android:paddingBottom="0dp"
                    android:text="NAME"
                    android:textColor="#fff"
                    android:textSize="24sp"
                    android:textStyle="bold" />

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/friend_activity_tv_age"
                        style="?android:attr/textAppearanceSmall"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="AGE"
                        android:textColor="#8fff"
                        android:textSize="16sp" />

                    <TextView
                        android:id="@+id/friend_activity_tv_country"
                        style="?android:attr/textAppearanceLarge"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="8dp"
                        android:paddingBottom="0dp"
                        android:text="COUNTRY"
                        android:textColor="#8fff"
                        android:textSize="16sp" />
                </LinearLayout>

            </LinearLayout>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView

        android:id="@+id/nested_scroll_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.CardView
                android:id="@+id/friend_activity_cardview_about"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp">

                <android.support.v7.widget.RecyclerView
                    android:id="@+id/recycler_list_friendDescription"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
                </android.support.v7.widget.RecyclerView>

            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:id="@+id/friend_activity_cardview_buttons_block"
                android:layout_below="@+id/friend_activity_cardview_about"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:layout_marginTop="8dp">

                <LinearLayout
                    android:id="@+id/my_profile_linear_likers_visitors"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <Button
                        android:id="@+id/friend_activity_btn_add_friend"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:backgroundTint="@android:color/white"
                        android:paddingLeft="25dp"
                        android:paddingRight="25dp"
                        android:text="+ ADD FRIEND"
                        android:textColor="@color/colorPrimary" />

                    <Button
                        android:id="@+id/friend_activity_btn_start_chat"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:backgroundTint="@android:color/white"
                        android:paddingLeft="25dp"
                        android:paddingRight="25dp"
                        android:text="WRITE MESSAGE"
                        android:textColor="@color/colorPrimary" />

                </LinearLayout>
            </android.support.v7.widget.CardView>

            <android.support.v7.widget.CardView
                android:id="@+id/friend_activity_cardview_list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@+id/friend_activity_cardview_buttons_block"
                android:layout_marginBottom="8dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <Button
                        android:id="@+id/settings_image_btn"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:backgroundTint="@android:color/white"
                        android:paddingLeft="25dp"
                        android:paddingRight="25dp"
                        android:text="@string/check_your_search_parameters"
                        android:textColor="@color/colorPrimary" />

                    <TextView
                        android:id="@+id/my_profile_tv_also_like"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_below="@+id/settings_image_btn"
                        android:layout_gravity="left"
                        android:layout_marginLeft="8dp"
                        android:layout_marginTop="8dp"
                        android:text="@string/you_may_also_like_them"
                        android:textSize="24sp"
                        android:textStyle="bold" />

                    <android.support.v7.widget.RecyclerView
                        android:id="@+id/my_profile_recyler"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_below="@+id/my_profile_tv_also_like" />
                </LinearLayout>

            </android.support.v7.widget.CardView>

        </RelativeLayout>

    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/friend_activity_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:src="@drawable/ic_favorite_black_24dp"
        app:fabSize="normal"
        app:layout_anchor="@id/app_bar_layout"
        app:layout_anchorGravity="bottom|end" />

</android.support.design.widget.CoordinatorLayout>

Body:
You have to programmatically set the height within onCreate

Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int width = size.x;

        AppBarLayout abl=findViewById(R.id.appbar);
        CoordinatorLayout.LayoutParams ap=(CoordinatorLayout.LayoutParams) abl.getLayoutParams();
        ap.height=width;

        abl.setLayoutParams(ap);

in the resource xml, on AppBarLayout
you also have to set

android:fitsSystemWindows="false"

otherwise the square will start from the very top (including status bar battery, notification etc.), unless you want the square to begin from there.


来源: stackexchange

问:
Javascript float subtract
I was wondering how can I subtract two negative Floating-Point numbers in javascript. I tried:

```<code>alert(-0.2-0.1);
</code>```

and the result is -0.30000000000000004. Am I doing something wrong? What do I have to do to get -0.3 ?

答:
You are doing nothing wrong, what you are seeing is the side effect of computers storing numbers in base 2. In base 10, 1/3 can't be precisely represented: .33333333 (with a bar over the 3). The same is true for 1/10 and 1/5 in base 2 or binary. The answer you see is merely the result of a rounding error. If you are working with money, it is often advised to just store values as cents to avoid some floating point errors.

As far as fixing the result you can do something like:

```<code>var SIGDIG= 100000000;
alert( Math.floor((-0.2-0.1)*SIGDIG)/SIGDIG );
</code>```

ContentLicense: CC BY-SA 3.0

Title: Javascript float subtract

Body:
I was wondering how can I subtract two negative Floating-Point numbers in javascript. I tried:

alert(-0.2-0.1);

and the result is -0.30000000000000004. Am I doing something wrong? What do I have to do to get -0.3 ?

Body:
You are doing nothing wrong, what you are seeing is the side effect of computers storing numbers in base 2. In base 10, 1/3 can't be precisely represented: .33333333 (with a bar over the 3). The same is true for 1/10 and 1/5 in base 2 or binary. The answer you see is merely the result of a rounding error. If you are working with money, it is often advised to just store values as cents to avoid some floating point errors.

As far as fixing the result you can do something like:

var SIGDIG= 100000000;
alert( Math.floor((-0.2-0.1)*SIGDIG)/SIGDIG );


问:
Difference between let arrayName = [...this.A, this.B] and let arrayName = [...this.A] arrayName.push(this.B)
So ive been watching freeCodeCamp's Vue Youtube Tutorial.

At one point they use this code:

```<code>let answers = [...this.currentQuestion.incorrect_answers];
answers.push(this.currentQuestion.correct_answer);
</code>```

And then this one:

```<code>let answers = [...this.currentQuestion.incorrect_answers, this.currentQuestion.correct_answer]
</code>```

Are they any different? Im more familiar with the push method, but if the other method works and doesnt have any downsides, why bother pushing?

答:
They are not different.

```<code>let answers = [...this.currentQuestion.incorrect_answers, this.currentQuestion.correct_answer]
</code>```

In above case you are just initialising the array with all values.

```<code>let answers = [...this.currentQuestion.incorrect_answers];
answers.push(this.currentQuestion.correct_answer);
</code>```

In above case you are initialising with all the contents of this.currentQuestion.incorrect_answers. After that you are pushing this.currentQuestion.correct_answer.

ContentLicense: CC BY-SA 4.0

Body:
So ive been watching freeCodeCamp's Vue Youtube Tutorial.

At one point they use this code:

let answers = [...this.currentQuestion.incorrect_answers];
answers.push(this.currentQuestion.correct_answer);

And then this one:

let answers = [...this.currentQuestion.incorrect_answers, this.currentQuestion.correct_answer]

Are they any different? Im more familiar with the push method, but if the other method works and doesnt have any downsides, why bother pushing?

Body:
They are not different.

let answers = [...this.currentQuestion.incorrect_answers, this.currentQuestion.correct_answer]

In above case you are just initialising the array with all values.

let answers = [...this.currentQuestion.incorrect_answers];
answers.push(this.currentQuestion.correct_answer);

In above case you are initialising with all the contents of this.currentQuestion.incorrect_answers. After that you are pushing this.currentQuestion.correct_answer.


问:
R: Reliable conversion between gtable and ggplot objects. How to make lemon::reposition_legend() work after ggplot_build()?
I have a pretty complicated case at hand with ggplot2. I tried to exemplify it with a MWE using iris data below.

I just have boxplots in facets, and wanted to move the legend to take the space of the empty facets.

This is all good, I use lemon::reposition_legend() for that and it works.

However, I then have to modify a bunch of things in the plot (namely add significant test results and other things that are not relevant for this question), and I am forced to use ggplot_build() on my output plot for that purpose.

After using ggplot_build() to modify my plot, I do not seem to be able to use reposition_legend() successfully anymore...

Check out my MWE below.

First I load the packages I need, and define a shift_legend() function (which uses reposition_legend()), based on an answer to this question.

```<code>library(tidyr)
library(ggplot2)
library(ggplotify)
library(gtable)
library(cowplot)
library(purrr)
library(lemon)
library(grid)
shift_legend <- function(p) {
  pnls <- NULL
  if (class(p)[1] == "gtable") pnls <- p
  else if (class(p)[2] == "ggplot") pnls <- plot_to_gtable(p)
  else stop("Please provide a ggplot or a gtable object")

  pnls <- gtable_filter(pnls, "panel")
  pnls <- setNames(pnls$grobs, pnls$layout$name)
  pnls <- keep(pnls, ~identical(.x, zeroGrob()))

  res <- NULL
  if(length(pnls) > 0) res <- reposition_legend( p, "center", panel=names(pnls) )
  else res <- p
  return(res)
}
</code>```

I then load the iris data and make my plot with shift_legend() successfully.

```<code>data(iris)
summary(iris)
iris_long <- gather(iris, "Variable", "Value", -Species)
P <- ggplot(iris_long, aes(x=Variable, y=Value)) +
  geom_boxplot(aes(fill=Variable), position=position_dodge(.9)) +
  facet_wrap(.~Species, ncol=2) +
  theme_light() +
  theme(legend.key.size = unit(0.5, "inch"))
out_file_name <- "test.pdf"
pdf(file=out_file_name, height=10, width=10, onefile=FALSE)
print(
  grid.draw(shift_legend(P))
)
dev.off()
</code>```

This produces this output, all good till here:

Note this is the arrangement I want to be able to reproduce (after using ggplot_build), with the legend taking the empty facets space.

But now I need to use ggplot_build() to add and modify things in my plot. After that I can plot it normally without using reposition_legend().

```<code>P2 <- ggplot_build(P)
#Do a bunch of things here...
out_file_name2 <- "test2.pdf"
pdf.options(reset=TRUE, onefile=FALSE)
pdf(file=out_file_name2, height=10, width=10)
print(
  plot(ggplot_gtable(P2))
)
dev.off()
</code>```

Which produces this:

But I still want to reposition the legend, so I attempt to use reposition_legend() again converting the ggplot_built object into a gtable object (which, according to the function documentation it can accept also as input).

```<code>out_file_name22 <- "test22.pdf"
pdf.options(reset=TRUE, onefile=FALSE)
pdf(file=out_file_name22, height=10, width=10)
print(
  grid.draw(shift_legend(
    ggplot_gtable(P2)
  ))
)
dev.off()
</code>```

Here I get this error:

Error in reposition_legend(p, "center", panel = names(pnls)) :
  No legend given in arguments, or could not extract legend from plot.

I tried again converting the gtable object into a ggplot one using ggplotify::as.ggplot(). This time I obtained no errors, but the legend was not repositioned as expected...

```<code>out_file_name222 <- "test222.pdf"
pdf.options(reset=TRUE, onefile=FALSE)
pdf(file=out_file_name222, height=10, width=10)
print(
  grid.draw(shift_legend(
    as.ggplot(ggplot_gtable(P2))
  ))
)
dev.off()
</code>```

It produces this:

Help please!

EDIT

I tried to change the workflow, as suggested in the comments and answers, to no avail.

Being P the original plot, what I need to modify is in the ggplot_build(P)$data data frame.

This data frame looks like this:

```<code>> ggplot_build(P)$data
[[1]]
      fill ymin lower middle upper ymax           outliers notchupper notchlower x PANEL group ymin_final ymax_final  xmin  xmax weight colour size alpha shape
1  #F8766D  1.2 1.400   1.50 1.575  1.7 1.1, 1.0, 1.9, 1.9  1.5391030  1.4608970 1     1     1        1.0        1.9 0.625 1.375      1 grey20  0.5    NA    19
2  #7CAE00  0.1 0.200   0.20 0.300  0.4           0.5, 0.6  0.2223446  0.1776554 2     1     2        0.1        0.6 1.625 2.375      1 grey20  0.5    NA    19
3  #00BFC4  4.3 4.800   5.00 5.200  5.8                     5.0893783  4.9106217 3     1     3        4.3        5.8 2.625 3.375      1 grey20  0.5    NA    19
4  #C77CFF  2.9 3.200   3.40 3.675  4.2           4.4, 2.3  3.5061367  3.2938633 4     1     4        2.3        4.4 3.625 4.375      1 grey20  0.5    NA    19
5  #F8766D  3.3 4.000   4.35 4.600  5.1                  3  4.4840674  4.2159326 1     2     1        3.0        5.1 0.625 1.375      1 grey20  0.5    NA    19
6  #7CAE00  1.0 1.200   1.30 1.500  1.8                     1.3670337  1.2329663 2     2     2        1.0        1.8 1.625 2.375      1 grey20  0.5    NA    19
7  #00BFC4  4.9 5.600   5.90 6.300  7.0                     6.0564120  5.7435880 3     2     3        4.9        7.0 2.625 3.375      1 grey20  0.5    NA    19
8  #C77CFF  2.0 2.525   2.80 3.000  3.4                     2.9061367  2.6938633 4     2     4        2.0        3.4 3.625 4.375      1 grey20  0.5    NA    19
9  #F8766D  4.5 5.100   5.55 5.875  6.9                     5.7231705  5.3768295 1     3     1        4.5        6.9 0.625 1.375      1 grey20  0.5    NA    19
10 #7CAE00  1.4 1.800   2.00 2.300  2.5                     2.1117229  1.8882771 2     3     2        1.4        2.5 1.625 2.375      1 grey20  0.5    NA    19
11 #00BFC4  5.6 6.225   6.50 6.900  7.9                4.9  6.6508259  6.3491741 3     3     3        4.9        7.9 2.625 3.375      1 grey20  0.5    NA    19
12 #C77CFF  2.5 2.800   3.00 3.175  3.6      3.8, 2.2, 3.8  3.0837922  2.9162078 4     3     4        2.2        3.8 3.625 4.375      1 grey20  0.5    NA    19
   linetype
1     solid
2     solid
3     solid
4     solid
5     solid
6     solid
7     solid
8     solid
9     solid
10    solid
11    solid
12    solid
</code>```

I modify aspects of it like annotation (not applicable in this MWE) and colour.

However, if, as suggested, I attempt to shift the legend of P before using ggplot_build() to extract and modify the relevant info, I have to do the following:

```<code>P2 <- as.ggplot(shift_legend(P))
ggplot_build(P2)$data
</code>```

The first command opens a new plotting window, which is undesired.

The second command produces this:

```<code>> ggplot_build(P2)$data
[[1]]
  x y PANEL group
1 0 0     1    -1
2 1 1     1    -1

[[2]]
  PANEL group xmin xmax ymin ymax
1     1    -1    0    1    0    1
</code>```

This looks nothing like the data data frame I modify in P... Any clue where to find it, if possible, in P2 now?

EDIT 2

Just so you see an example of my real life boxplots to see why modifying ggplot_build(P)$data is important to me.

There is no way to show only significant pairwise comparisons with geom_signif().

What I do is use geom_signif() with dummy text to populate the annotation data frame I can access at ggplot_build(P)$data[[3]], and then add my actual significance values to the $annotation column, and subset the data frame accordingly to show only significant comparisons. There I have full control, and can change the colors of the comparisons according to significance, which group has a higher mean, etc, etc.

I asked this a while ago here, and ever since I have polished this and wrapped it into a function.

As you see, this clashes with my shift_legend function, as I do not seem to find a way to access the data data frame...

This is what I have so far with my real life data, I placed the legend at the bottom, but it would be optimal that it took the empty facets space, especially cause I have cases where there are more empty facets.

答:
I have revised this answer based on further information from the OP.

We start by loading the libraries and creating the plot. For this example I have added an extra layer of text objects that can be manipulated in the resulting ggplot_built object, as the OP requires:

library(tidyr)
library(ggplot2)
library(ggplotify)
library(gtable)
library(cowplot)
library(purrr)
library(lemon)
library(grid)

data(iris)

iris_long   <- gather(iris, "Variable", "Value", -Species)
text_labels <- data.frame(text = "Text", x = 2, y = 3, stringsAsFactors = FALSE)

P <- ggplot(iris_long, aes(x = Variable, y = Value)) +
     geom_boxplot(aes(fill = Variable), position = position_dodge(.9)) +
     geom_text(data = text_labels, aes(x = x, y = y, label = text)) +
     facet_wrap(.~Species, ncol = 2) +
     theme_light() +
     theme(legend.key.size = unit(0.5, "inch"))
```

<p>Now we convert to the <code>ggplot_built</code> object and manipulate it as necessary. Here, we'll just manually change the colour of the text through <code>P2$data[[2]]</code></p>

```# Convert to ggplot_built
P2 <- ggplot_build(P)

# Do stuff with P2$data
P2$data[[2]]$colour <- rep("red", 3)

# We have changed P2 successfully
grid.draw(ggplot_gtable(P2))
```

<p><img src="https://i.stack.imgur.com/cW4ud.png" alt="enter image description here"></p>

<p>Now we want to add the legend to the facet. We grab a copy of the legend from our plot using <code>ggplot_gtable</code>:</p>

<pre class="lang-r prettyprint-override"><code>P3 <- reposition_legend(ggplot_gtable(P2), "center",
                        legend = g_legend(ggplot_gtable(P2)),
                        panel = "panel-2-2")
</code>```

However, this creates a new problem: we have our correctly placed legend, but we also have the old one that we no longer want:

We then fix this by finding the grob we don't want and overwriting it with a zerogrob:

legend_grob <- which(sapply(P3$grobs, function(x) x$name) == "guide-box")
P3$grobs[[legend_grob]] <- zeroGrob()
```

<p>Now, we will still have a blank space at the right side of our plot that we don't want, so we apply a negative pad to the right:</p>

```P3 <- gtable_add_padding(P3, unit(c(0,-.15, 0, 0), "npc")
```

<p>Now we can plot the result with <code>grid.draw</code>:</p>

```grid.newpage()
grid.draw(P3)
```

<p><img src="https://i.stack.imgur.com/RIYMB.png" alt="enter image description here"></p>

<p>Note that we have preserved the changes we made manually to the <code>ggplot_built</code> object.</p>

<p>So your function to transform a <code>ggplot_built</code> object into a plot with the legend moved to the facet would be something like:</p>

```legend_as_facet <- function(P2)
{
  # Convert the ggplot_built object to a gtable
  P2       <- ggplot_gtable(P2)

  # Find the name of the panel on the bottom right of the plot
  panels   <- grep("panel", P2$layout$name, value = TRUE)
  panelmat <- sapply(strsplit(panels, "-"), function(x) as.numeric(x[2:3]))
  maxpanel <- paste("panel", max(panelmat[2,]), max(panelmat[2,]), sep = "-")

  # Draw the legend in the bottom right panel
  P3 <- reposition_legend(P2, "center", legend = g_legend(P2), panel = maxpanel)

  # Draw a zero grob in place of the existing legend
  legend_grob <- which(sapply(P3$grobs, function(x) x$name) == "guide-box")
  P3$grobs[[legend_grob]] <- zeroGrob()

  # Apply negative padding to remove the empty space on the right
  P3 <- gtable_add_padding(P3, unit(c(0,-.15, 0, 0), "npc"))

  # Draw the result
  grid.newpage()
  grid.draw(P3)
}
```

<p>This means your work flow would be:</p>

```P2 <- ggplot_build(P)

# Do stuff with P2$data

legend_as_facet(P2)
```

Created on 2020-02-19 by the reprex package (v0.3.0)

Body:
I have a pretty complicated case at hand with ggplot2. I tried to exemplify it with a MWE using iris data below.

I just have boxplots in facets, and wanted to move the legend to take the space of the empty facets.

This is all good, I use lemon::reposition_legend() for that and it works.

However, I then have to modify a bunch of things in the plot (namely add significant test results and other things that are not relevant for this question), and I am forced to use ggplot_build() on my output plot for that purpose.

After using ggplot_build() to modify my plot, I do not seem to be able to use reposition_legend() successfully anymore...

Check out my MWE below.

First I load the packages I need, and define a shift_legend() function (which uses reposition_legend()), based on an answer to this question.

library(tidyr)
library(ggplot2)
library(ggplotify)
library(gtable)
library(cowplot)
library(purrr)
library(lemon)
library(grid)
shift_legend <- function(p) {
  pnls <- NULL
  if (class(p)[1] == "gtable") pnls <- p
  else if (class(p)[2] == "ggplot") pnls <- plot_to_gtable(p)
  else stop("Please provide a ggplot or a gtable object")

  pnls <- gtable_filter(pnls, "panel")
  pnls <- setNames(pnls$grobs, pnls$layout$name)
  pnls <- keep(pnls, ~identical(.x, zeroGrob()))

  res <- NULL
  if(length(pnls) > 0) res <- reposition_legend( p, "center", panel=names(pnls) )
  else res <- p
  return(res)
}

I then load the iris data and make my plot with shift_legend() successfully.

data(iris)
summary(iris)
iris_long <- gather(iris, "Variable", "Value", -Species)
P <- ggplot(iris_long, aes(x=Variable, y=Value)) +
  geom_boxplot(aes(fill=Variable), position=position_dodge(.9)) +
  facet_wrap(.~Species, ncol=2) +
  theme_light() +
  theme(legend.key.size = unit(0.5, "inch"))
out_file_name <- "test.pdf"
pdf(file=out_file_name, height=10, width=10, onefile=FALSE)
print(
  grid.draw(shift_legend(P))
)
dev.off()

This produces this output, all good till here:

Note this is the arrangement I want to be able to reproduce (after using ggplot_build), with the legend taking the empty facets space.

But now I need to use ggplot_build() to add and modify things in my plot. After that I can plot it normally without using reposition_legend().

P2 <- ggplot_build(P)
#Do a bunch of things here...
out_file_name2 <- "test2.pdf"
pdf.options(reset=TRUE, onefile=FALSE)
pdf(file=out_file_name2, height=10, width=10)
print(
  plot(ggplot_gtable(P2))
)
dev.off()

Which produces this:

But I still want to reposition the legend, so I attempt to use reposition_legend() again converting the ggplot_built object into a gtable object (which, according to the function documentation it can accept also as input).

out_file_name22 <- "test22.pdf"
pdf.options(reset=TRUE, onefile=FALSE)
pdf(file=out_file_name22, height=10, width=10)
print(
  grid.draw(shift_legend(
    ggplot_gtable(P2)
  ))
)
dev.off()

Here I get this error:

Error in reposition_legend(p, "center", panel = names(pnls)) :
  No legend given in arguments, or could not extract legend from plot.

I tried again converting the gtable object into a ggplot one using ggplotify::as.ggplot(). This time I obtained no errors, but the legend was not repositioned as expected...

out_file_name222 <- "test222.pdf"
pdf.options(reset=TRUE, onefile=FALSE)
pdf(file=out_file_name222, height=10, width=10)
print(
  grid.draw(shift_legend(
    as.ggplot(ggplot_gtable(P2))
  ))
)
dev.off()

It produces this:

Help please!

EDIT

I tried to change the workflow, as suggested in the comments and answers, to no avail.

Being P the original plot, what I need to modify is in the ggplot_build(P)$data data frame.

This data frame looks like this:

> ggplot_build(P)$data
[[1]]
      fill ymin lower middle upper ymax           outliers notchupper notchlower x PANEL group ymin_final ymax_final  xmin  xmax weight colour size alpha shape
1  #F8766D  1.2 1.400   1.50 1.575  1.7 1.1, 1.0, 1.9, 1.9  1.5391030  1.4608970 1     1     1        1.0        1.9 0.625 1.375      1 grey20  0.5    NA    19
2  #7CAE00  0.1 0.200   0.20 0.300  0.4           0.5, 0.6  0.2223446  0.1776554 2     1     2        0.1        0.6 1.625 2.375      1 grey20  0.5    NA    19
3  #00BFC4  4.3 4.800   5.00 5.200  5.8                     5.0893783  4.9106217 3     1     3        4.3        5.8 2.625 3.375      1 grey20  0.5    NA    19
4  #C77CFF  2.9 3.200   3.40 3.675  4.2           4.4, 2.3  3.5061367  3.2938633 4     1     4        2.3        4.4 3.625 4.375      1 grey20  0.5    NA    19
5  #F8766D  3.3 4.000   4.35 4.600  5.1                  3  4.4840674  4.2159326 1     2     1        3.0        5.1 0.625 1.375      1 grey20  0.5    NA    19
6  #7CAE00  1.0 1.200   1.30 1.500  1.8                     1.3670337  1.2329663 2     2     2        1.0        1.8 1.625 2.375      1 grey20  0.5    NA    19
7  #00BFC4  4.9 5.600   5.90 6.300  7.0                     6.0564120  5.7435880 3     2     3        4.9        7.0 2.625 3.375      1 grey20  0.5    NA    19
8  #C77CFF  2.0 2.525   2.80 3.000  3.4                     2.9061367  2.6938633 4     2     4        2.0        3.4 3.625 4.375      1 grey20  0.5    NA    19
9  #F8766D  4.5 5.100   5.55 5.875  6.9                     5.7231705  5.3768295 1     3     1        4.5        6.9 0.625 1.375      1 grey20  0.5    NA    19
10 #7CAE00  1.4 1.800   2.00 2.300  2.5                     2.1117229  1.8882771 2     3     2        1.4        2.5 1.625 2.375      1 grey20  0.5    NA    19
11 #00BFC4  5.6 6.225   6.50 6.900  7.9                4.9  6.6508259  6.3491741 3     3     3        4.9        7.9 2.625 3.375      1 grey20  0.5    NA    19
12 #C77CFF  2.5 2.800   3.00 3.175  3.6      3.8, 2.2, 3.8  3.0837922  2.9162078 4     3     4        2.2        3.8 3.625 4.375      1 grey20  0.5    NA    19
   linetype
1     solid
2     solid
3     solid
4     solid
5     solid
6     solid
7     solid
8     solid
9     solid
10    solid
11    solid
12    solid

I modify aspects of it like annotation (not applicable in this MWE) and colour.

However, if, as suggested, I attempt to shift the legend of P before using ggplot_build() to extract and modify the relevant info, I have to do the following:

P2 <- as.ggplot(shift_legend(P))
ggplot_build(P2)$data

The first command opens a new plotting window, which is undesired.

The second command produces this:

> ggplot_build(P2)$data
[[1]]
  x y PANEL group
1 0 0     1    -1
2 1 1     1    -1

[[2]]
  PANEL group xmin xmax ymin ymax
1     1    -1    0    1    0    1

This looks nothing like the data data frame I modify in P... Any clue where to find it, if possible, in P2 now?

EDIT 2

Just so you see an example of my real life boxplots to see why modifying ggplot_build(P)$data is important to me.

There is no way to show only significant pairwise comparisons with geom_signif().

What I do is use geom_signif() with dummy text to populate the annotation data frame I can access at ggplot_build(P)$data[[3]], and then add my actual significance values to the $annotation column, and subset the data frame accordingly to show only significant comparisons. There I have full control, and can change the colors of the comparisons according to significance, which group has a higher mean, etc, etc.

I asked this a while ago here, and ever since I have polished this and wrapped it into a function.

As you see, this clashes with my shift_legend function, as I do not seem to find a way to access the data data frame...

This is what I have so far with my real life data, I placed the legend at the bottom, but it would be optimal that it took the empty facets space, especially cause I have cases where there are more empty facets.

Body:
I have revised this answer based on further information from the OP.

We start by loading the libraries and creating the plot. For this example I have added an extra layer of text objects that can be manipulated in the resulting ggplot_built object, as the OP requires:

library(tidyr)
library(ggplot2)
library(ggplotify)
library(gtable)
library(cowplot)
library(purrr)
library(lemon)
library(grid)

data(iris)

iris_long   <- gather(iris, "Variable", "Value", -Species)
text_labels <- data.frame(text = "Text", x = 2, y = 3, stringsAsFactors = FALSE)

P <- ggplot(iris_long, aes(x = Variable, y = Value)) +
     geom_boxplot(aes(fill = Variable), position = position_dodge(.9)) +
     geom_text(data = text_labels, aes(x = x, y = y, label = text)) +
     facet_wrap(.~Species, ncol = 2) +
     theme_light() +
     theme(legend.key.size = unit(0.5, "inch"))

Now we convert to the ggplot_built object and manipulate it as necessary. Here, we'll just manually change the colour of the text through P2$data[[2]]

# Convert to ggplot_built
P2 <- ggplot_build(P)

# Do stuff with P2$data
P2$data[[2]]$colour <- rep("red", 3)

# We have changed P2 successfully
grid.draw(ggplot_gtable(P2))

Now we want to add the legend to the facet. We grab a copy of the legend from our plot using ggplot_gtable:

P3 <- reposition_legend(ggplot_gtable(P2), "center",
                        legend = g_legend(ggplot_gtable(P2)),
                        panel = "panel-2-2")

However, this creates a new problem: we have our correctly placed legend, but we also have the old one that we no longer want:

We then fix this by finding the grob we don't want and overwriting it with a zerogrob:

legend_grob <- which(sapply(P3$grobs, function(x) x$name) == "guide-box")
P3$grobs[[legend_grob]] <- zeroGrob()

Now, we will still have a blank space at the right side of our plot that we don't want, so we apply a negative pad to the right:

P3 <- gtable_add_padding(P3, unit(c(0,-.15, 0, 0), "npc")

Now we can plot the result with grid.draw:

grid.newpage()
grid.draw(P3)

Note that we have preserved the changes we made manually to the ggplot_built object.

So your function to transform a ggplot_built object into a plot with the legend moved to the facet would be something like:

legend_as_facet <- function(P2)
{
  # Convert the ggplot_built object to a gtable
  P2       <- ggplot_gtable(P2)

  # Find the name of the panel on the bottom right of the plot
  panels   <- grep("panel", P2$layout$name, value = TRUE)
  panelmat <- sapply(strsplit(panels, "-"), function(x) as.numeric(x[2:3]))
  maxpanel <- paste("panel", max(panelmat[2,]), max(panelmat[2,]), sep = "-")

  # Draw the legend in the bottom right panel
  P3 <- reposition_legend(P2, "center", legend = g_legend(P2), panel = maxpanel)

  # Draw a zero grob in place of the existing legend
  legend_grob <- which(sapply(P3$grobs, function(x) x$name) == "guide-box")
  P3$grobs[[legend_grob]] <- zeroGrob()

  # Apply negative padding to remove the empty space on the right
  P3 <- gtable_add_padding(P3, unit(c(0,-.15, 0, 0), "npc"))

  # Draw the result
  grid.newpage()
  grid.draw(P3)
}

This means your work flow would be:

P2 <- ggplot_build(P)

# Do stuff with P2$data

legend_as_facet(P2)

Created on 2020-02-19 by the reprex package (v0.3.0)


问:
Trouble posting data to website with axios
How can I send this data that I'm getting to an other website? I need to use axios and express, that's why it's with axios and not request. I tried some stuff but it keeps saying undefined.

```<code>async function rainbowAPI(error, Results, body) {
  await axios
    //Get data van json file pc only
    .get(`${baseUrl}search=${username}`, {})
    .then(Response => {
      //PlayerId en playerName weergeven in console
      //Er is ook nog p_user wat niet altijd matcht met p_id
      //p_user kan gebruikt worden om de player avatar te krijgen
      console.log(`PlayerId   ${Response.data.results[0].p_id}`)
      console.log(`UserName   ${Response.data.results[0].p_name}`)
      //Als de naam die ingegeven werd geen bestaande user is.
      if (Response.data.results[0].p_id === undefined) {
        //Status 400 versturen
      }
      //Als het een bestaande user is, verstuur je de data
      else {
        return Response;
      }
    })
    .catch((err) => {
      //Foutafhandeling
      console.error(err);
    });
};
</code>```

答:
I think you forgot to check that result is array or not
and you have to p_id exists or not in that result object then you can proceed to send that data to another request </p>

```<code>  await axios
            //Get data van json file pc only
            .get(`${baseUrl}search=${username}`, {
            })
            .then(async (Response) => {
                    //PlayerId en playerName weergeven in console
                    //Er is ook nog p_user wat niet altijd matcht met p_id
                    //p_user kan gebruikt worden om de player avatar te krijgen

                    // first check results object
                    if(Response.data.hasOwnProperty("results") && len(Response.data.results) ){
                             if(Response.data.results[0].hasOwnProperty("p_id")){
                             // now p_id exists
                              console.log(`PlayerId   ${Response.data.results[0].p_id}`)
                    console.log(`UserName   ${Response.data.results[0].p_name}`)
                    //Als de naam die ingegeven werd geen bestaande user is.
                    if(Response.data.results[0].p_id === undefined){
                        //Status 400 versturen
                    }
                //Als het een bestaande user is, verstuur je de data
                    else{
                        return Response;
                    }
}
                     }
              })
            .catch((err) => {
                //Foutafhandeling
                console.error(err);
            });
}
</code>```

Body:
How can I send this data that I'm getting to an other website? I need to use axios and express, that's why it's with axios and not request. I tried some stuff but it keeps saying undefined.

async function rainbowAPI(error, Results, body) {
  await axios
    //Get data van json file pc only
    .get(`${baseUrl}search=${username}`, {})
    .then(Response => {
      //PlayerId en playerName weergeven in console
      //Er is ook nog p_user wat niet altijd matcht met p_id
      //p_user kan gebruikt worden om de player avatar te krijgen
      console.log(`PlayerId   ${Response.data.results[0].p_id}`)
      console.log(`UserName   ${Response.data.results[0].p_name}`)
      //Als de naam die ingegeven werd geen bestaande user is.
      if (Response.data.results[0].p_id === undefined) {
        //Status 400 versturen
      }
      //Als het een bestaande user is, verstuur je de data
      else {
        return Response;
      }
    })
    .catch((err) => {
      //Foutafhandeling
      console.error(err);
    });
};

Body:
I think you forgot to check that result is array or not
and you have to p_id exists or not in that result object then you can proceed to send that data to another request

  await axios
            //Get data van json file pc only
            .get(`${baseUrl}search=${username}`, {
            })
            .then(async (Response) => {
                    //PlayerId en playerName weergeven in console
                    //Er is ook nog p_user wat niet altijd matcht met p_id
                    //p_user kan gebruikt worden om de player avatar te krijgen

                    // first check results object
                    if(Response.data.hasOwnProperty("results") && len(Response.data.results) ){
                             if(Response.data.results[0].hasOwnProperty("p_id")){
                             // now p_id exists
                              console.log(`PlayerId   ${Response.data.results[0].p_id}`)
                    console.log(`UserName   ${Response.data.results[0].p_name}`)
                    //Als de naam die ingegeven werd geen bestaande user is.
                    if(Response.data.results[0].p_id === undefined){
                        //Status 400 versturen
                    }
                //Als het een bestaande user is, verstuur je de data
                    else{
                        return Response;
                    }
}
                     }
              })
            .catch((err) => {
                //Foutafhandeling
                console.error(err);
            });
}


问:
Valid Lighthouse URL patterns
I can see that the Lighthouse CLI accepts a --blocked-url-patterns argument, but I can't find any definition of what constitutes a valid pattern, only that * is supported.

Is * literally the only supported pattern-ish character?

答:
In this Google Lighthouse test file blockedUrlPatterns is an array of string with various patterns.

```<code>blockedUrlPatterns: ['http://*.evil.com', '.jpg', '.woff2'],
</code>```

Title: Valid Lighthouse URL patterns

Body:
In this Google Lighthouse test file blockedUrlPatterns is an array of string with various patterns.

blockedUrlPatterns: ['http://*.evil.com', '.jpg', '.woff2'],


问:
Angular 9 TestBed.inject & Provider Overrides
When using provider overrides what is the alternative of the following now that TestBed.get has been deprecated in Angular 9

```<code>TestBed.configureTestingModule({
  providers: [{ provide: MyClass, useClass: MyStub}]
});

const obj : MyStub = TestBed.get(MyClass);
</code>```

Is it really this or is there a better way?

```<code>const obj : MyStub = TestBed.inject(MyClass) as unknown as MyStub;
</code>```

答:
For all intents and purposes, your MyStub should at least be a Partial or a class that extends the class it's trying to mock, otherwise your tests are kinda 'wrong', so if that's the case you can just do:

```<code>const obj = TestBed.inject(MyClass);
</code>```

If you somehow will have different properties or different function signatures on your stub, you can also do this:

```<code>const obj = TestBed.inject<MyStub>(MyClass as any);
</code>```

But generally speaking, your mocks should (partially) share the same signature as the thing it's mocking, which also means there is no need for casting

Body:
When using provider overrides what is the alternative of the following now that TestBed.get has been deprecated in Angular 9

TestBed.configureTestingModule({
  providers: [{ provide: MyClass, useClass: MyStub}]
});

const obj : MyStub = TestBed.get(MyClass);

Is it really this or is there a better way?

const obj : MyStub = TestBed.inject(MyClass) as unknown as MyStub;

Body:
For all intents and purposes, your MyStub should at least be a Partial or a class that extends the class it's trying to mock, otherwise your tests are kinda 'wrong', so if that's the case you can just do:

const obj = TestBed.inject(MyClass);

If you somehow will have different properties or different function signatures on your stub, you can also do this:

const obj = TestBed.inject<MyStub>(MyClass as any);

But generally speaking, your mocks should (partially) share the same signature as the thing it's mocking, which also means there is no need for casting


问:
Use double click and single click simultaneously in react native

Hi, Thanks in advance, am using the Double click Component and it
  works well for double click event. But I need to get an action when
  user perform a single click. What the work around for this issue.

```<code>  <DoubleClick  onClick={(e) => this.hClick(value,e)}>
               <View>
                  <Text>
                    {value.item}
                  </Text>
               </View>
          </DoubleClick>
</code>```

答:
I wrote a component.

// @flow
import * as React from 'react';
import { TouchableOpacity } from 'react-native';
import type { PressEvent } from 'react-native/Libraries/Types/CoreEventTypes';

type Props = {
  children?: any,
  onSingleTap: (event: PressEvent) => void,
  onDoubleTap: (event: PressEvent) => void,
};

const MAX_DOUBLE_TOUCH_DISTANCE = 20;
const MAX_DOUBLE_TOUCH_DELAY_TIME = 250;

class SingleDoubleTap extends React.Component<Props> {
  _timer: TimeoutID;
  _previousPressEvent: ?PressEvent;

  onPress = (event: PressEvent) => {
    if (this._previousPressEvent) {
      this.onReceiveSecondEvent(event);
    } else {
      this.onReceiveFirstEvent(event);
    }
  };

  onReceiveFirstEvent = (event: PressEvent) => {
    this._timer = setTimeout(() => {
      this.props.onSingleTap(event);
      this._previousPressEvent = null;
    }, MAX_DOUBLE_TOUCH_DELAY_TIME);
    this._previousPressEvent = event;
  };

  onReceiveSecondEvent = (event: PressEvent) => {
    if (this._isDoubleTap(event)) {
      this.props.onDoubleTap(event);
    } else {
      this.props.onSingleTap(event);
    }

    this._timer && clearTimeout(this._timer);
    this._previousPressEvent = null;
  };

  _distanceBetweenTouches = (
    touch1: PressEvent,
    touch2: PressEvent
  ): number => {
    const disX = touch1.nativeEvent.locationX - touch2.nativeEvent.locationX;
    const disY = touch1.nativeEvent.locationY - touch2.nativeEvent.locationY;
    return Math.sqrt(Math.pow(disX, 2) + Math.pow(disY, 2));
  };

  _isDoubleTap = (currentEvent: PressEvent) => {
    if (!this._previousPressEvent) {
      return false;
    }

    const distance = this._distanceBetweenTouches(
      currentEvent,
      this._previousPressEvent
    );

    // $FlowFixMe
    const { nativeEvent } = this._previousPressEvent;
    const delay = currentEvent.nativeEvent.timestamp - nativeEvent.timestamp;
    return (
      distance < MAX_DOUBLE_TOUCH_DISTANCE &&
      delay < MAX_DOUBLE_TOUCH_DELAY_TIME
    );
  };

  componentWillUnmount = () => {
    this._timer && clearTimeout(this._timer);
  };

  render() {
    return (
      <TouchableOpacity onPress={this.onPress}>
        {this.props.children}
      </TouchableOpacity>
    );
  }
}

export default SingleDoubleTap;
```

<p>How to use it? </p>

<pre class="lang-js prettyprint-override"><code><SingleDoubleTap
   onSingleTap={this._onSingleTap}
   onDoubleTap={this._onDoubleTap}>
  ..... // other components
</SingleDoubleTap>

</code>```

The key thing is you should wait for the DoubleTap event failed to recognize the touch event as OneTap.

Body:
Hi, Thanks in advance, am using the Double click Component and it
  works well for double click event. But I need to get an action when
  user perform a single click. What the work around for this issue.

  <DoubleClick  onClick={(e) => this.hClick(value,e)}>
               <View>
                  <Text>
                    {value.item}
                  </Text>
               </View>
          </DoubleClick>

Body:
I wrote a component.

// @flow
import * as React from 'react';
import { TouchableOpacity } from 'react-native';
import type { PressEvent } from 'react-native/Libraries/Types/CoreEventTypes';

type Props = {
  children?: any,
  onSingleTap: (event: PressEvent) => void,
  onDoubleTap: (event: PressEvent) => void,
};

const MAX_DOUBLE_TOUCH_DISTANCE = 20;
const MAX_DOUBLE_TOUCH_DELAY_TIME = 250;

class SingleDoubleTap extends React.Component<Props> {
  _timer: TimeoutID;
  _previousPressEvent: ?PressEvent;

  onPress = (event: PressEvent) => {
    if (this._previousPressEvent) {
      this.onReceiveSecondEvent(event);
    } else {
      this.onReceiveFirstEvent(event);
    }
  };

  onReceiveFirstEvent = (event: PressEvent) => {
    this._timer = setTimeout(() => {
      this.props.onSingleTap(event);
      this._previousPressEvent = null;
    }, MAX_DOUBLE_TOUCH_DELAY_TIME);
    this._previousPressEvent = event;
  };

  onReceiveSecondEvent = (event: PressEvent) => {
    if (this._isDoubleTap(event)) {
      this.props.onDoubleTap(event);
    } else {
      this.props.onSingleTap(event);
    }

    this._timer && clearTimeout(this._timer);
    this._previousPressEvent = null;
  };

  _distanceBetweenTouches = (
    touch1: PressEvent,
    touch2: PressEvent
  ): number => {
    const disX = touch1.nativeEvent.locationX - touch2.nativeEvent.locationX;
    const disY = touch1.nativeEvent.locationY - touch2.nativeEvent.locationY;
    return Math.sqrt(Math.pow(disX, 2) + Math.pow(disY, 2));
  };

  _isDoubleTap = (currentEvent: PressEvent) => {
    if (!this._previousPressEvent) {
      return false;
    }

    const distance = this._distanceBetweenTouches(
      currentEvent,
      this._previousPressEvent
    );

    // $FlowFixMe
    const { nativeEvent } = this._previousPressEvent;
    const delay = currentEvent.nativeEvent.timestamp - nativeEvent.timestamp;
    return (
      distance < MAX_DOUBLE_TOUCH_DISTANCE &&
      delay < MAX_DOUBLE_TOUCH_DELAY_TIME
    );
  };

  componentWillUnmount = () => {
    this._timer && clearTimeout(this._timer);
  };

  render() {
    return (
      <TouchableOpacity onPress={this.onPress}>
        {this.props.children}
      </TouchableOpacity>
    );
  }
}

export default SingleDoubleTap;

How to use it?

<SingleDoubleTap
   onSingleTap={this._onSingleTap}
   onDoubleTap={this._onDoubleTap}>
  ..... // other components
</SingleDoubleTap>

The key thing is you should wait for the DoubleTap event failed to recognize the touch event as OneTap.