Explore all Grid open source software, libraries, packages, source code, cloud functions and APIs.

Popular New Releases in Grid

react-virtualized

handsontable

react-grid-layout

1.3.4

react-table

v7.7.0

bootstrap-table

v1.19.1

Popular Libraries in Grid

react-virtualized

by bvaughn doticonjavascriptdoticon

star image 22397 doticonMIT

React components for efficiently rendering large lists and tabular data

handsontable

by handsontable doticonjavascriptdoticon

star image 16512 doticonNOASSERTION

JavaScript data grid with a spreadsheet look & feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡

react-grid-layout

by react-grid-layout doticonjavascriptdoticon

star image 15452 doticonMIT

A draggable and resizable grid layout with responsive breakpoints, for React.

masonry

by desandro doticonhtmldoticon

star image 15330 doticon

:love_hotel: Cascading grid layout plugin

react-table

by tannerlinsley doticonjavascriptdoticon

star image 14517 doticonMIT

⚛️ Hooks for building fast and extendable tables and datagrids for React

react-grid-layout

by STRML doticonjavascriptdoticon

star image 12934 doticonMIT

A draggable and resizable grid layout with responsive breakpoints, for React.

bootstrap-table

by wenzhixin doticonjavascriptdoticon

star image 11189 doticonMIT

An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation, Vue.js)

react-window

by bvaughn doticonjavascriptdoticon

star image 11126 doticonMIT

React components for efficiently rendering large lists and tabular data

flexboxgrid

by kristoferjoseph doticonhtmldoticon

star image 9197 doticonNOASSERTION

Grid based on CSS3 flexbox

Trending New libraries in Grid

gridjs

by grid-js doticontypescriptdoticon

star image 3364 doticonMIT

Advanced table plugin

react-virtual

by tannerlinsley doticonjavascriptdoticon

star image 2497 doticonMIT

⚛️ Hooks for virtualizing scrollable elements in React

revogrid

by revolist doticontypescriptdoticon

star image 1991 doticonMIT

Powerful virtual data grid smartsheet with advanced customization. Best features from excel plus incredible performance 🔋

material-ui-x

by mui-org doticontypescriptdoticon

star image 1066 doticon

MUI X: Build complex and data-rich applications using a growing list of advanced components. We're kicking it off with the most powerful Data Grid on the market.

glide-data-grid

by glideapps doticontypescriptdoticon

star image 1011 doticonMIT

🦝 Glide Data Grid is a no compromise, outrageously fast data grid for your React project, with rich rendering, first class accessibility, and full TypeScript support.

laravel-livewire-tables

by rappasoft doticonphpdoticon

star image 982 doticonMIT

A dynamic table component for Laravel Livewire - For Slack access, visit:

Grid

by exyte doticonswiftdoticon

star image 811 doticonMIT

The most powerful Grid container missed in SwiftUI

ali-react-table

by alibaba doticontypescriptdoticon

star image 766 doticonMIT

Performent, flexible and modern React table component.

S2

by antvis doticontypescriptdoticon

star image 613 doticonMIT

⚡️ Practical analytical Table rendering core lib.

Top Authors in Grid

1

codrops

14 Libraries

star icon2398

2

loiane

12 Libraries

star icon80

3

handsontable

8 Libraries

star icon18603

4

metafizzy

8 Libraries

star icon4029

5

DevExpress-Examples

7 Libraries

star icon26

6

revolist

7 Libraries

star icon2171

7

grid-js

6 Libraries

star icon3481

8

ag-grid

6 Libraries

star icon8477

9

flexiblegs

6 Libraries

star icon113

10

frictionlessdata

5 Libraries

star icon149

1

14 Libraries

star icon2398

2

12 Libraries

star icon80

3

8 Libraries

star icon18603

4

8 Libraries

star icon4029

5

7 Libraries

star icon26

6

7 Libraries

star icon2171

7

6 Libraries

star icon3481

8

6 Libraries

star icon8477

9

6 Libraries

star icon113

10

5 Libraries

star icon149

Trending Kits in Grid

Here are some of the famous React Grid Libraries. React Grid Libraries use cases include Dynamic Data Tables, Responsive Layouts, Drag and Drop, and Spreadsheet-like Interfaces. 


React grid libraries are packages of code that provide developers with a way to create responsive and customizable grids within React applications. They allow developers to create and manipulate grids and tables and offer features such as sorting and filtering. React grid libraries are popular within web development as they provide an easy way to create and maintain complex layouts. 


Let us have a look at these libraries in detail. 

react-virtualized 

  • Supports dynamic column widths and row heights. 
  • Designed to work with any type of data, including text, images, and React components. 
  • Uses a virtual rendering technique to render huge datasets with minimal memory overhead.   

react-grid-layout 

  • Responsive Breakpoint. 
  • Layout Persistence. 
  • Drag & Drop. 

react-table 

  • Responsive and scales automatically. 
  • Supports server-side rendering. 
  • Has a lightweight codebase and is highly optimized for performance. 

flexboxgrid 

  • Ability to nest grids and utilize media queries. 
  • Support for both left-to-right (LTR) and right-to-left (RTL) languages. 
  • Support for Flexbox, allowing developers to create flexible layouts with automatic equal-height columns. 

react-data-grid 

  • Provides a number of plugins and extension points. 
  • Includes a set of tools to make development easier. 
  • Virtualized rendering approach eliminates the need for a large DOM and reduces the number of elements rendered. 

react-flexbox-grid 

  • Built-in support for offsetting columns and gutters. 
  • Offers a powerful system for nesting grids. 
  • Provides a column-based media query system. 

react-bootstrap-table 

  • Offers an extensive API that allows you to interact easily with the table.  
  • Library offers a wide range of styling options for you to use.  
  • You can easily add, delete, or update records in the table without updating the DOM manually. 

react-masonry-component 

  • Supports animations on grid items. 
  • Includes an image loader allowing easy preloading of images before displaying them in the grid. 
  • Supports filtering of the grid items. 

react-grid-system 

  • Modular, allowing developers to choose only the pieces they need for their project. 
  • Offers a flexible column system, which allows developers to create complex layouts easily. 
  • All components are fully accessible to users with disabilities. 

Matplotlib is a well-known Python visualization library. It offers various functions and comprehensive documentation for building various plot categories. It also provides a high level of customization. Users can change their visualizations' colors, fonts, axes, and labels. One of Matplotlib's key advantages is its ability to create interactive visualizations.  

 

Matplotlib's widget package includes several interactive widgets. It allows users to include interactivity and communication in their plots. Whether working with plots or creating individual plots, the Pyplot module enhances your visualizations. With Matplotlib, you can create line plots, scatter plots, box plots, and more.  

 

Using NumPy integration, you can plot data from arrays and work with various datasets. If you're creating a `single figure` with subplots, Matplotlib provides the necessary tools. It creates common layouts, or you can build your custom layouts.  

 

From basic to advanced features, Matplotlib offers versatile tools for data visualization. With Matplotlib, you have the flexibility to create different types of plots. You can arrange them in custom layouts using the `subplot2grid` function. You can customize the appearance of your visualizations to convey your data's insights.  

 

subplot2grid function: 

Matplotlib creates custom layouts of subplots using the subplot2grid function. With this function, you can define a grid of cells and specify the location. You can specify the size of each subplot within the grid. This allows arranging many plots in a single figure with different configurations. Using this function, you can create complex arrangements within a single figure.  

 

The subplot2grid function provides flexibility in designing different layouts. It includes column subplots, bottom subplots, and adjacent subplots. It allows you to create subplots that span many rows or columns. You can create subplots in separate cells or a subplot that occupies the entire row. It enables creating `inset axes` or subplots with a `shared y-axis.`  

 

Syntax of subplot2grid: 

The subplot2grid function takes grid, loc, and optional arguments: rowspan and colspan. Here's a breakdown of these arguments:  

  • grid: It specifies the grid layout of the subplots. It takes a tuple (nrows, ncols) that defines the grid's number of rows and columns. For example, grid = (3, 3) represents a 3x3 grid.  
  • loc: It determines the starting cell position of the subplot within the grid. It takes a tuple(row, col) that specifies the row and column indices where the subplot will be placed.  
  • rowspan: It specifies the number of rows the subplot will span within the grid. It is set to 1 by default, meaning the subplot occupies a single row.  
  • colspan: It will specify the number of columns the subplot will span within the grid. It is set to 1 by default, indicating that the subplot occupies a single column. 


By manipulating the arguments, you can create subplots. They can span many rows or columns, allowing for various layout configurations.  

 

Using subplot2grid (grid, (0,0),rowspan=2), you can create a subplot that starts at the top-left cell (0, 0) and spans two rows. This is useful when you want to create a larger subplot that covers many cells.  

 

You can create a subplot in the bottom row using subplot2grid (grid, (2, 0), colspan=3). It spans the entire width of the grid. This is convenient when you want to create a subplot. It occupies the entire row, such as a summary or aggregated plot.  

 

Combining subplot2grid allows you to create sophisticated and appealing layouts for your subplots. This flexibility allows designing complex figures that convey insights from your data. 


Preview of the output obtained when subplot2grid function is used.

Code

A 3x2 grid of subplots are created using subplot2grid and set the face color of each subplot is set to dark blue-gray color using the facecolor parameter.

Follow the steps carefully to get the output easily.

  • Install Visual Studio Code in your computer.
  • Install the required library by using the following command - pip install matplotlib
  • If your system is not reflecting the installation, try running the above command by opening windows powershell as administrator.
  • Open the folder in the code editor, copy and paste the above kandi code snippet in the python file.
  • Add the line import matplotlib.pyplot as plt in the beginning.
  • Run the code using the run command.


I hope you found this useful. I have added the link to dependent libraries, version information in the following sections.


I found this code snippet by searching for "subplots in Matplotlib using the subplot2grid function" in kandi. You can try any such use case!

Dependent Libraries

If you do not have matplotlib that is required to run this code, you can install it by clicking on the above link and copying the pip Install command from the page in kandi.


You can search for any dependent library on kandi like matplotlib.

Environment tested

  1. This code had been tested using python version 3.8.0
  2. matplotlib version 3.7.1 has been used.

Support

  1. For any support on kandi solution kits, please use the chat
  2. For further learning resources, visit the Open Weaver Community learning page.

FAQ 

1. What is Matplotlib, and how does it relate to Pyplot?  

Matplotlib is a Python library. It helps in creating static, animated, and interactive visualizations. Pyplot is a module within Matplotlib. It provides a simplified interface for creating plots and charts.  

 

2. How can I create a column subplot with many plots in Matplotlib? 

You can use the subplots() function to create a column subplot layout. Then use the returned Axes objects to plot your plots using the plot() function.  

 

3. How do I share an axis between different plots in pyplot?  

You can use the sharex or sharey parameter to share an axis between plots when creating the subplots.  

 

4. Can a figure containing subplots with the enclosing figure object be drawn?  

Yes. It draws a figure containing subplots using the enclosing figure object. You can achieve this by specifying the number of rows and columns for the subplot's layout. Then accessing each subplot through the returned axes object.  

 

5. Does having many subplots on one figure affect performance if the number of data points is large?  

Having many subplots on one figure can affect performance if the number of data points is large. Each subplot requires resources to render and display the data. The more subplots you have, the more resources are required. If you have many data points and subplots, it leads to increased memory usage. It also leads to longer rendering times. This can impact the responsiveness or the time it takes to generate and display the plots. 

A grid plot is also known as a grid chart or grid display. It is a visual data representation that organizes information in a grid-like structure. It analyzes and distributes data that has two or more variables or dimensions. A grid plot consists of a series of cells or small squares arranged in rows and columns. Each cell represents a unique combination of values from the variables being analyzed.  

 

The cells are filled with color, line style, shading, or patterns. It helps indicate the magnitude or intensity of the data. Grid plots are useful for visualizing patterns, relationships, and distributions within complex datasets. It compares and contrasts data across different combinations of variables. It identifies trends or anomalies. By organizing data in a grid format, grid alignment provides a systematic way. It helps explore multidimensional data and gain insights.  

 

Grid plots can visualize various data types. It includes numeric data, categorical data, and Mixed Data. Numeric values can be represented using Heatmaps, Contour plots, and Surface plots. Categorical Data represents Contingency tables, Stacked charts, Clustered column charts, and Mixed data. It can be represented by using Combination plots, small multiples.  

 

Grid plot data can create various plots, including bar, grid lines, scatter, and Box Plots. The choice of subplots depends on the data characteristics and the analysis objectives. Effective decision-making involves a combination of Identify and Analyze Trends. It is based on Detect Outliers and Anomalies and Compare Patterns and Relationships. It enhances identifying outliers and patterns, leading to better insights and decision-making. Interpreting grid plot data involves analyzing the patterns, relationships, and variations. It is done within the plot to make informed decisions.  

 

Here are some steps to help you interpret grid plot data:  

  • Understand the Variables  
  • Analyze Patterns  
  • Identify Relationships  
  • Spot Outliers or Anomalies  
  • Consider Context  


Using grid plot data not only enhances the quality of decision-making. But it also contributes to the development of data analysis skills. Regular engagement sharpens your ability to identify patterns, interpret visualizations, and extract insights. It strengthens critical thinking, hypothesis generation, and the analytical mindset for data analysis. In conclusion, leveraging grid plot data empowers you. It makes well-informed decisions, uncovers hidden insights, and improves data analysis skills. It drives success in various domains and disciplines. 


Here is an example of creating grid plot with shared axes.



Fig1: Preview of the Code and output.

Code


In this solution, we are creating grid plot with shared axes and legends.

Instructions

Follow the steps carefully to get the output easily.

  1. Install Jupyter Notebook on your computer.
  2. Open terminal and install the required libraries with following commands.
  3. Install matplotlib - pip install matplotlib.
  4. Copy the code using the "Copy" button above and paste it into your IDE's Python file.
  5. Run the file.


I hope you found this useful. I have added the link to dependent libraries, version information in the following sections.


I found this code snippet by searching for "Create grid plot with shared axes and legends" in kandi. You can try any such use case!

Dependent Libraries


If you do not have matplotlib or numpy that is required to run this code, you can install it by clicking on the above link and copying the pip Install command from the respective page in kandi.


You can search for any dependent library on kandi like matplotlib

Environment Tested


I tested this solution in the following versions. Be mindful of changes when working with other versions.

  1. The solution is created in Python 3.9.6
  2. The solution is tested on matplotlib version 3.5.0


Using this solution, we are able to create grid plots with legends and shared axes.

Support

  1. For any support on kandi solution kits, please use the chat
  2. For further learning resources, visit the Open Weaver Community learning page.


FAQ:  

1. How do I draw grid lines in a grid plot in Python? 

You can draw grid lines in a grid plot, depending on the specific visualization library you are using. Here are examples using two popular libraries, Matplotlib and Seaborn:  

  • Using Matplotlib: To draw grid lines in a Matplotlib plot, you can use the grid() function. It can be applied to an existing plot or a subplot within a grid plot.  
  • Using Seaborn: Seaborn is a higher-level library built on top of Matplotlib. You can use the sns.set_style() function with the grid() option to draw grid lines in a Seaborn plot.  


2. What are the advantages of using histogram plots over other plots?  

Histogram plots offer several advantages, making them useful for data analysis tasks. Here are some advantages of using histogram plots:  

  • Distribution Visualization  
  • Bin Selection  
  • Data Summarization  
  • Easy Comparison  
  • Identifying Outliers  
  • Data Preprocessing  

 

3. How can I ensure proper grid alignment when creating a plot in Python?  

To ensure proper grid alignment when creating a plot in Python, you can follow these steps:  

  • Determine the desired number of rows and columns for your grid layout. This will depend on the number of subplots you want to create and the desired arrangement.  
  • Use the plotting library to create subplots within the specified grid layout.  
  • When creating each subplot, specify the indices to position them within the grid. The indices start from 1 and increment as you move across rows and columns.  
  • Fine-tune the spacing between subplots to ensure proper alignment.  

 

4. What is the NumPy library, and how can it be used to create a grid plot?  

The NumPy library is a fundamental Python package for scientific computing. It provides powerful tools and functions. It helps to work with multidimensional arrays, linear algebra, and random number generation. NumPy serves as the foundation for many other scientific computing libraries in Python.  

 

While NumPy does not have built-in functions, you can use it with libraries to create them. NumPy's ability helps handle numerical operations and array manipulations. This makes it a valuable tool for data preprocessing and manipulation. It helps in computation in preparation for creating grid plots using visualization libraries. It allows for advanced data processing and analysis. It helps generate grid plot data and perform data computations before plotting.  

 

5. Is there an easy way to create a basic plot with Python libraries?  

Yes, several Python libraries provide an easy way to create basic plots. Two popular libraries for creating basic plots in Python are Matplotlib and Seaborn.  

Matplotlib:  

Matplotlib is a used plotting library. It provides a flexible and comprehensive set of functions. It helps in creating various types of plots.  

Seaborn:  

Seaborn is built on top of Matplotlib. It provides a higher-level interface for creating pleasing statistical visualizations. Both libraries offer various customization options. It allows you to create more complex and appealing plots.  

When working with ReactJS, it's common to need to display data from an array of objects in a grid layout. This can be useful for displaying tables, lists, or any other structured data type.

 

To accomplish this, you can use the map() method to loop through the array of objects and generate a JSX element for each project. You can display the object's attributes within each element in a grid layout CSS. In this solution kit, I am sharing the code snippet that can be used to display a simple image in a grid form.



PS: Preview of the output that you will get on running this code from your IDE

Code

In this solution we use the displayGrid and gridItem CSS .class Selector to achieve it.

  1. Copy the code using the "Copy" button above, and paste it in a react project in your IDE.
  2. add the displayGrid to the grid element and the gridItem to the items present inside the object array (any HTML element can be added in it).
  3. Run the application to see the object array as a grid.


I found this code snippet by searching for "object array as a grid in ReactJs" in kandi. You can try any such use case!

Dependent Libraries

If you do not have react application that is required to run this code, you can install it by clicking on the above link of react page in kandi.

You can search for any dependent library on kandi like React.

Environment Tested

I tested this solution in the following versions. Be mindful of changes when working with other versions.

  1. The solution is created in react v18.2.0
  2. The solution is tested on chrome browser


Support

  1. For any support on kandi solution kits, please use the chat
  2. For further learning resources, visit the Open Weaver Community learning page.

A data table is the most convenient and obvious solution for storing and manipulating data on any web application. But for someone who works on data-heavy business apps, making these data tables readable and clear is a major fix. But the data table should be flexible enough to adapt to your database, so you can filter, format, add, edit, and remove data easily. With so many great Vue.js components now, creating such tables is possible. With that in mind, here are some of the best Vue Table libraries in 2022. handsontable - JavaScript data grid with a spreadsheet look; bootstrap-table - extended table; vue-grid-layout - A draggable and resizable grid layout, for Vue.js.

Trending Discussions on Grid

Material-UI Data Grid onSortModelChange Causing an Infinite Loop

How to fix position image on another image

How to automate legends for a new geom in ggplot2?

How to log production database changes made via the Django shell

Memoize multi-dimensional recursive solutions in haskell

Is it possible to combine a ggplot legend and table

Centre CSS Grid Items Dependent On Dynamic Content Count

Using cowplot in R to make a ggplot chart occupy two consecutive rows

Stopping CSS Grid column from overflowing

logistic regression and GridSearchCV using python sklearn

QUESTION

Material-UI Data Grid onSortModelChange Causing an Infinite Loop

Asked 2022-Feb-14 at 23:31

I'm following the Sort Model documentation (https://material-ui.com/components/data-grid/sorting/#basic-sorting) and am using sortModel and onSortModelChange exactly as used in the documentation. However, I'm getting an infinite loop immediately after loading the page (I can tell this based on the console.log).

What I've tried:

I always end up with the same issue. I'm using Blitz.js.

My code:

useState:

1const [sortModel, setSortModel] = useState<GridSortModel>([
2    {
3      field: "updatedAt",
4      sort: "desc" as GridSortDirection,
5    },
6  ])
7

rows definition:

1const [sortModel, setSortModel] = useState<GridSortModel>([
2    {
3      field: "updatedAt",
4      sort: "desc" as GridSortDirection,
5    },
6  ])
7  const rows = currentUsersApplications.map((application) => {
8    return {
9      id: application.id,
10      business_name: application.business_name,
11      business_phone: application.business_phone,
12      applicant_name: application.applicant_name,
13      applicant_email: application.applicant_email,
14      owner_cell_phone: application.owner_cell_phone,
15      status: application.status,
16      agent_name: application.agent_name,
17      equipment_description: application.equipment_description,
18      createdAt: formattedDate(application.createdAt),
19      updatedAt: formattedDate(application.updatedAt),
20      archived: application.archived,
21    }
22  })
23

columns definition:

1const [sortModel, setSortModel] = useState<GridSortModel>([
2    {
3      field: "updatedAt",
4      sort: "desc" as GridSortDirection,
5    },
6  ])
7  const rows = currentUsersApplications.map((application) => {
8    return {
9      id: application.id,
10      business_name: application.business_name,
11      business_phone: application.business_phone,
12      applicant_name: application.applicant_name,
13      applicant_email: application.applicant_email,
14      owner_cell_phone: application.owner_cell_phone,
15      status: application.status,
16      agent_name: application.agent_name,
17      equipment_description: application.equipment_description,
18      createdAt: formattedDate(application.createdAt),
19      updatedAt: formattedDate(application.updatedAt),
20      archived: application.archived,
21    }
22  })
23
24  const columns = [
25    { field: "id", headerName: "ID", width: 70, hide: true },
26    {
27      field: "business_name",
28      headerName: "Business Name",
29      width: 200,
30      // Need renderCell() here because this is a link and not just a string
31      renderCell: (params: GridCellParams) => {
32        console.log(params)
33        return <BusinessNameLink application={params.row} />
34      },
35    },
36    { field: "business_phone", headerName: "Business Phone", width: 180 },
37    { field: "applicant_name", headerName: "Applicant Name", width: 180 },
38    { field: "applicant_email", headerName: "Applicant Email", width: 180 },
39    { field: "owner_cell_phone", headerName: "Ownership/Guarantor Phone", width: 260 },
40    { field: "status", headerName: "Status", width: 130 },
41    { field: "agent_name", headerName: "Agent", width: 130 },
42    { field: "equipment_description", headerName: "Equipment", width: 200 },
43    { field: "createdAt", headerName: "Submitted At", width: 250 },
44    { field: "updatedAt", headerName: "Last Edited", width: 250 },
45    { field: "archived", headerName: "Archived", width: 180, type: "boolean" },
46  ]
47

Rendering DataGrid and using sortModel/onSortChange

1const [sortModel, setSortModel] = useState<GridSortModel>([
2    {
3      field: "updatedAt",
4      sort: "desc" as GridSortDirection,
5    },
6  ])
7  const rows = currentUsersApplications.map((application) => {
8    return {
9      id: application.id,
10      business_name: application.business_name,
11      business_phone: application.business_phone,
12      applicant_name: application.applicant_name,
13      applicant_email: application.applicant_email,
14      owner_cell_phone: application.owner_cell_phone,
15      status: application.status,
16      agent_name: application.agent_name,
17      equipment_description: application.equipment_description,
18      createdAt: formattedDate(application.createdAt),
19      updatedAt: formattedDate(application.updatedAt),
20      archived: application.archived,
21    }
22  })
23
24  const columns = [
25    { field: "id", headerName: "ID", width: 70, hide: true },
26    {
27      field: "business_name",
28      headerName: "Business Name",
29      width: 200,
30      // Need renderCell() here because this is a link and not just a string
31      renderCell: (params: GridCellParams) => {
32        console.log(params)
33        return <BusinessNameLink application={params.row} />
34      },
35    },
36    { field: "business_phone", headerName: "Business Phone", width: 180 },
37    { field: "applicant_name", headerName: "Applicant Name", width: 180 },
38    { field: "applicant_email", headerName: "Applicant Email", width: 180 },
39    { field: "owner_cell_phone", headerName: "Ownership/Guarantor Phone", width: 260 },
40    { field: "status", headerName: "Status", width: 130 },
41    { field: "agent_name", headerName: "Agent", width: 130 },
42    { field: "equipment_description", headerName: "Equipment", width: 200 },
43    { field: "createdAt", headerName: "Submitted At", width: 250 },
44    { field: "updatedAt", headerName: "Last Edited", width: 250 },
45    { field: "archived", headerName: "Archived", width: 180, type: "boolean" },
46  ]
47      <div style={{ height: 580, width: "100%" }} className={classes.gridRowColor}>
48        <DataGrid
49          getRowClassName={(params) => `MuiDataGrid-row--${params.getValue(params.id, "status")}`}
50          rows={rows}
51          columns={columns}
52          pageSize={10}
53          components={{
54            Toolbar: GridToolbar,
55          }}
56          filterModel={{
57            items: [{ columnField: "archived", operatorValue: "is", value: showArchived }],
58          }}
59          sortModel={sortModel}
60          onSortModelChange={(model) => {
61            console.log(model)
62            //Infinitely logs model immediately
63            setSortModel(model)
64          }}
65        />
66      </div>
67

Thanks in advance!

ANSWER

Answered 2021-Aug-31 at 19:57

I fixed this by wrapping rows and columns in useRefs and used their .current property for both of them. Fixed it immediately.

Source https://stackoverflow.com/questions/69004286

QUESTION

How to fix position image on another image

Asked 2022-Feb-14 at 08:23

I have the following code (also pasted below), where I want to make a layout of two columns. In the first one I am putting two images, and in the second displaying some text.

In the first column, I want to have the first image with width:70% and the second one with position:absolute on it. The final result should be like this

As you see the second image partially located in first one in every screens above to 768px.

I can partially locate second image on first one, but that is not dynamic, if you change screen dimensions you can see how that collapse.

But no matter how hard I try, I can not achieve this result.

1.checkoutWrapper {
2  display: grid;
3  grid-template-columns: 50% 50%;
4}
5
6.coverIMGLast {
7  width: 70%;
8  height: 100vh;
9}
10
11/* this .phone class should be dynamically  located partially on first image */
12.phone {
13  position: absolute;
14  height: 90%;
15  top: 5%;
16  bottom: 5%;
17  margin-left: 18%;
18}
19
20.CheckoutProcess {
21  padding: 5.8rem 6.4rem;
22}
23
24.someContent {
25  border: 1px solid red;
26}
27
28/* removing for demo purposes
29@media screen and (max-width: 768px) {
30  .checkoutWrapper {
31    display: grid;
32    grid-template-columns: 100%;
33  }
34  img {
35    display: none;
36  }
37  .CheckoutProcess {
38    padding: 0;
39  }
40}
41*/
1.checkoutWrapper {
2  display: grid;
3  grid-template-columns: 50% 50%;
4}
5
6.coverIMGLast {
7  width: 70%;
8  height: 100vh;
9}
10
11/* this .phone class should be dynamically  located partially on first image */
12.phone {
13  position: absolute;
14  height: 90%;
15  top: 5%;
16  bottom: 5%;
17  margin-left: 18%;
18}
19
20.CheckoutProcess {
21  padding: 5.8rem 6.4rem;
22}
23
24.someContent {
25  border: 1px solid red;
26}
27
28/* removing for demo purposes
29@media screen and (max-width: 768px) {
30  .checkoutWrapper {
31    display: grid;
32    grid-template-columns: 100%;
33  }
34  img {
35    display: none;
36  }
37  .CheckoutProcess {
38    padding: 0;
39  }
40}
41*/<div class="checkoutWrapper">
42  <img src="https://via.placeholder.com/300" class="coverIMGLast" />
43  <img src="https://via.placeholder.com/300/ff0000" class="phone" />
44
45  <div class="CheckoutProcess">
46    <Content />
47  </div>
48</div>

ANSWER

Answered 2022-Feb-07 at 08:19

With the code below, you have the structure that you want. All you have to do is to play with the width, height, etc to make exactly what you need.

1.checkoutWrapper {
2  display: grid;
3  grid-template-columns: 50% 50%;
4}
5
6.coverIMGLast {
7  width: 70%;
8  height: 100vh;
9}
10
11/* this .phone class should be dynamically  located partially on first image */
12.phone {
13  position: absolute;
14  height: 90%;
15  top: 5%;
16  bottom: 5%;
17  margin-left: 18%;
18}
19
20.CheckoutProcess {
21  padding: 5.8rem 6.4rem;
22}
23
24.someContent {
25  border: 1px solid red;
26}
27
28/* removing for demo purposes
29@media screen and (max-width: 768px) {
30  .checkoutWrapper {
31    display: grid;
32    grid-template-columns: 100%;
33  }
34  img {
35    display: none;
36  }
37  .CheckoutProcess {
38    padding: 0;
39  }
40}
41*/<div class="checkoutWrapper">
42  <img src="https://via.placeholder.com/300" class="coverIMGLast" />
43  <img src="https://via.placeholder.com/300/ff0000" class="phone" />
44
45  <div class="CheckoutProcess">
46    <Content />
47  </div>
48</div>.checkoutWrapper {
49  display: grid;
50  grid-template-columns: 50% 50%;
51}
52
53.checkoutImgs {
54  position: relative;
55  border: 2px solid red;
56}
57
58img{
59  object-fit:cover;
60  display:block;
61}
62
63.coverIMGLast {
64  width: 70%;
65  height: 100vh;
66  
67}
68
69  
70.phone {
71  position: absolute;
72  height: 80%;
73  width:40%;
74  top: 10%;
75  right: 0;
76
77}
78
79.CheckoutProcess {
80  padding: 5.8rem 6.4rem;
81  border: 2px solid blue;
82}
83
84
85
86/* removing for demo purposes
87@media screen and (max-width: 768px) {
88  .checkoutWrapper {
89    display: grid;
90    grid-template-columns: 100%;
91  }
92  img {
93    display: none;
94  }
95  .CheckoutProcess {
96    padding: 0;
97  }
98}
99*/
1.checkoutWrapper {
2  display: grid;
3  grid-template-columns: 50% 50%;
4}
5
6.coverIMGLast {
7  width: 70%;
8  height: 100vh;
9}
10
11/* this .phone class should be dynamically  located partially on first image */
12.phone {
13  position: absolute;
14  height: 90%;
15  top: 5%;
16  bottom: 5%;
17  margin-left: 18%;
18}
19
20.CheckoutProcess {
21  padding: 5.8rem 6.4rem;
22}
23
24.someContent {
25  border: 1px solid red;
26}
27
28/* removing for demo purposes
29@media screen and (max-width: 768px) {
30  .checkoutWrapper {
31    display: grid;
32    grid-template-columns: 100%;
33  }
34  img {
35    display: none;
36  }
37  .CheckoutProcess {
38    padding: 0;
39  }
40}
41*/<div class="checkoutWrapper">
42  <img src="https://via.placeholder.com/300" class="coverIMGLast" />
43  <img src="https://via.placeholder.com/300/ff0000" class="phone" />
44
45  <div class="CheckoutProcess">
46    <Content />
47  </div>
48</div>.checkoutWrapper {
49  display: grid;
50  grid-template-columns: 50% 50%;
51}
52
53.checkoutImgs {
54  position: relative;
55  border: 2px solid red;
56}
57
58img{
59  object-fit:cover;
60  display:block;
61}
62
63.coverIMGLast {
64  width: 70%;
65  height: 100vh;
66  
67}
68
69  
70.phone {
71  position: absolute;
72  height: 80%;
73  width:40%;
74  top: 10%;
75  right: 0;
76
77}
78
79.CheckoutProcess {
80  padding: 5.8rem 6.4rem;
81  border: 2px solid blue;
82}
83
84
85
86/* removing for demo purposes
87@media screen and (max-width: 768px) {
88  .checkoutWrapper {
89    display: grid;
90    grid-template-columns: 100%;
91  }
92  img {
93    display: none;
94  }
95  .CheckoutProcess {
96    padding: 0;
97  }
98}
99*/<div class="checkoutWrapper">
100  <div class="checkoutImgs">
101    <img src="https://via.placeholder.com/300" class="coverIMGLast" />
102    <img src="https://via.placeholder.com/300/ff0000" class="phone" />
103  </div>
104
105  <div class="CheckoutProcess">
106    <Content />
107  </div>
108</div>

Source https://stackoverflow.com/questions/70989863

QUESTION

How to automate legends for a new geom in ggplot2?

Asked 2022-Jan-30 at 18:08

I've built this new ggplot2 geom layer I'm calling geom_triangles (see https://github.com/ctesta01/ggtriangles/) that plots isosceles triangles given aesthetics including x, y, z where z is the height of the triangle and the base of the isosceles triangle has midpoint (x,y) on the graph.

What I want is for the geom_triangles() layer to automatically provide legend components for the height and width of the triangles, but I am not sure how to do that.

I understand based on this reference that I may need to adjust the draw_key argument in the ggproto StatTriangles object, but I'm not sure how I would do that and can't seem to find examples online of how to do it. I've been looking at the source code in ggplot2 for the draw_key functions, but I'm not sure how I would introduce multiple legend components (one for each of height and width) in a single draw_key argument in the StatTriangles ggproto.

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130

first plot example with mtcars, geom_triangles, and color legend

What I have been able to do is to write helper functions (draw_geom_triangles_height_legend, draw_geom_triangles_width_legend) and use the patchwork, and cowplot packages to make legend components rather manually and combining them in an appropriate grid with the original plot, but I want to make producing these legend components automatic. The following code also uses the ggrepel package to add text labels in the figure.

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223

second plot with mtcars, geom_triangles, with added legend components for height and width

What I'm looking for is to be able to run the code for the first plot example and get a legend with 3 components similar to the color/fill, height, and width legend components as in the second plot example.

Unfortunately the helper functions are not at all satisfactory because at present one has to rely on visually estimating whether the legend's height_scale and width_scale components look correct. This is because the lengeds produced by draw_geom_triangles_height_legend and draw_geom_triangles_width_legend are their own ggplot objects and therefore aren't necessarily on the same coordinate scaling system as the main ggplot of interest for which they are supposed to be legends.

Both of the plots I included are rendered at 7in x 8.5in using ggsave.

Here's my R sessionInfo()

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246

ANSWER

Answered 2022-Jan-30 at 18:08

I think you might be slightly overcomplicating things. Ideally, you'd just want a single key drawing method for the whole layer. However, because you're using a Stat to do the majority of calculations, this becomes hairy to implement. In my answer, I'm avoiding this.

Let's say I'd want to use a geom-only implementation of such a layer. I can make the following (simplified) class/constructor pair. Below, I haven't bothered width_scale or height_scale parameters, just for simplicity.

Class
1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246library(ggplot2)
247
248GeomTriangles <- ggproto(
249  "GeomTriangles", GeomPoint,
250  default_aes = aes(
251    colour = "black", fill = "black", size = 0.5, linetype = 1, 
252    alpha = 1, angle = 0, width = 0.5, height = 0.5
253  ),
254  
255  draw_panel = function(
256    data, panel_params, coord, na.rm = FALSE
257  ) {
258    # Apply coordinate transform
259    df <- coord$transform(data, panel_params)
260    
261    # Repeat every row 3x
262    idx <- rep(seq_len(nrow(df)), each = 3)
263    rep_df <- df[idx, ]
264    # Calculate offsets from origin
265    x_off <- as.vector(outer(c(-0.5, 0, 0.5), df$width))
266    y_off <- as.vector(outer(c(0, 1, 0), df$height))
267    
268    # Rotate offsets
269    ang <- rep_df$angle * (pi / 180)
270    x_new <- x_off * cos(ang) - y_off * sin(ang)
271    y_new <- x_off * sin(ang) + y_off * cos(ang)
272    
273    # Combine offsets with origin
274    x <- unit(rep_df$x, "npc") + unit(x_new, "cm")
275    y <- unit(rep_df$y, "npc") + unit(y_new, "cm")
276    
277    grid::polygonGrob(
278      x = x, y = y, id = idx,
279      gp = grid::gpar(
280        col  = alpha(df$colour, df$alpha),
281        fill = alpha(df$fill, df$alpha),
282        lwd  = df$size * .pt,
283        lty  = df$linetype
284      )
285    )
286  }
287)
288
Constructor
1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246library(ggplot2)
247
248GeomTriangles <- ggproto(
249  "GeomTriangles", GeomPoint,
250  default_aes = aes(
251    colour = "black", fill = "black", size = 0.5, linetype = 1, 
252    alpha = 1, angle = 0, width = 0.5, height = 0.5
253  ),
254  
255  draw_panel = function(
256    data, panel_params, coord, na.rm = FALSE
257  ) {
258    # Apply coordinate transform
259    df <- coord$transform(data, panel_params)
260    
261    # Repeat every row 3x
262    idx <- rep(seq_len(nrow(df)), each = 3)
263    rep_df <- df[idx, ]
264    # Calculate offsets from origin
265    x_off <- as.vector(outer(c(-0.5, 0, 0.5), df$width))
266    y_off <- as.vector(outer(c(0, 1, 0), df$height))
267    
268    # Rotate offsets
269    ang <- rep_df$angle * (pi / 180)
270    x_new <- x_off * cos(ang) - y_off * sin(ang)
271    y_new <- x_off * sin(ang) + y_off * cos(ang)
272    
273    # Combine offsets with origin
274    x <- unit(rep_df$x, "npc") + unit(x_new, "cm")
275    y <- unit(rep_df$y, "npc") + unit(y_new, "cm")
276    
277    grid::polygonGrob(
278      x = x, y = y, id = idx,
279      gp = grid::gpar(
280        col  = alpha(df$colour, df$alpha),
281        fill = alpha(df$fill, df$alpha),
282        lwd  = df$size * .pt,
283        lty  = df$linetype
284      )
285    )
286  }
287)
288geom_triangles <- function(mapping = NULL, data = NULL,
289                           position = "identity", na.rm = FALSE, show.legend = NA,
290                           inherit.aes = TRUE, ...) {
291  layer(
292    stat = "identity", geom = GeomTriangles, data = data, mapping = mapping,
293    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
294    params = list(na.rm = na.rm, ...)
295  )
296}
297
Example

Just to show how it works without any special keys set. I'm letting a continuous scale for width and height take over the job of your width_scale and height_scale parameters, because I didn't want to focus on that here. As you can see, two legends are made automatically, but with the wrong glyphs.

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246library(ggplot2)
247
248GeomTriangles <- ggproto(
249  "GeomTriangles", GeomPoint,
250  default_aes = aes(
251    colour = "black", fill = "black", size = 0.5, linetype = 1, 
252    alpha = 1, angle = 0, width = 0.5, height = 0.5
253  ),
254  
255  draw_panel = function(
256    data, panel_params, coord, na.rm = FALSE
257  ) {
258    # Apply coordinate transform
259    df <- coord$transform(data, panel_params)
260    
261    # Repeat every row 3x
262    idx <- rep(seq_len(nrow(df)), each = 3)
263    rep_df <- df[idx, ]
264    # Calculate offsets from origin
265    x_off <- as.vector(outer(c(-0.5, 0, 0.5), df$width))
266    y_off <- as.vector(outer(c(0, 1, 0), df$height))
267    
268    # Rotate offsets
269    ang <- rep_df$angle * (pi / 180)
270    x_new <- x_off * cos(ang) - y_off * sin(ang)
271    y_new <- x_off * sin(ang) + y_off * cos(ang)
272    
273    # Combine offsets with origin
274    x <- unit(rep_df$x, "npc") + unit(x_new, "cm")
275    y <- unit(rep_df$y, "npc") + unit(y_new, "cm")
276    
277    grid::polygonGrob(
278      x = x, y = y, id = idx,
279      gp = grid::gpar(
280        col  = alpha(df$colour, df$alpha),
281        fill = alpha(df$fill, df$alpha),
282        lwd  = df$size * .pt,
283        lty  = df$linetype
284      )
285    )
286  }
287)
288geom_triangles <- function(mapping = NULL, data = NULL,
289                           position = "identity", na.rm = FALSE, show.legend = NA,
290                           inherit.aes = TRUE, ...) {
291  layer(
292    stat = "identity", geom = GeomTriangles, data = data, mapping = mapping,
293    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
294    params = list(na.rm = na.rm, ...)
295  )
296}
297ggplot(mtcars, aes(mpg, disp, height = cyl, width = wt, colour = hp, fill = hp)) +
298  geom_triangles() +
299  geom_point(colour = "black") +
300  continuous_scale("width", "wscale",  
301                   palette = scales::rescale_pal(c(0.1, 0.5))) +
302  continuous_scale("height", "hscale", 
303                   palette = scales::rescale_pal(c(0.1, 0.5)))
304

Glyphs

Writing a function to draw a glyph isn't too difficult. In this case, we do almost the same as GeomTriangles$draw_panel, but we fix the x and y positions of the origin, and don't use a coordinate transform.

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246library(ggplot2)
247
248GeomTriangles <- ggproto(
249  "GeomTriangles", GeomPoint,
250  default_aes = aes(
251    colour = "black", fill = "black", size = 0.5, linetype = 1, 
252    alpha = 1, angle = 0, width = 0.5, height = 0.5
253  ),
254  
255  draw_panel = function(
256    data, panel_params, coord, na.rm = FALSE
257  ) {
258    # Apply coordinate transform
259    df <- coord$transform(data, panel_params)
260    
261    # Repeat every row 3x
262    idx <- rep(seq_len(nrow(df)), each = 3)
263    rep_df <- df[idx, ]
264    # Calculate offsets from origin
265    x_off <- as.vector(outer(c(-0.5, 0, 0.5), df$width))
266    y_off <- as.vector(outer(c(0, 1, 0), df$height))
267    
268    # Rotate offsets
269    ang <- rep_df$angle * (pi / 180)
270    x_new <- x_off * cos(ang) - y_off * sin(ang)
271    y_new <- x_off * sin(ang) + y_off * cos(ang)
272    
273    # Combine offsets with origin
274    x <- unit(rep_df$x, "npc") + unit(x_new, "cm")
275    y <- unit(rep_df$y, "npc") + unit(y_new, "cm")
276    
277    grid::polygonGrob(
278      x = x, y = y, id = idx,
279      gp = grid::gpar(
280        col  = alpha(df$colour, df$alpha),
281        fill = alpha(df$fill, df$alpha),
282        lwd  = df$size * .pt,
283        lty  = df$linetype
284      )
285    )
286  }
287)
288geom_triangles <- function(mapping = NULL, data = NULL,
289                           position = "identity", na.rm = FALSE, show.legend = NA,
290                           inherit.aes = TRUE, ...) {
291  layer(
292    stat = "identity", geom = GeomTriangles, data = data, mapping = mapping,
293    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
294    params = list(na.rm = na.rm, ...)
295  )
296}
297ggplot(mtcars, aes(mpg, disp, height = cyl, width = wt, colour = hp, fill = hp)) +
298  geom_triangles() +
299  geom_point(colour = "black") +
300  continuous_scale("width", "wscale",  
301                   palette = scales::rescale_pal(c(0.1, 0.5))) +
302  continuous_scale("height", "hscale", 
303                   palette = scales::rescale_pal(c(0.1, 0.5)))
304draw_key_triangle <- function(data, params, size) {
305  # browser()
306  idx <- rep(seq_len(nrow(data)), each = 3)
307  rep_data <- data[idx, ]
308  
309  x_off <- as.vector(outer(
310    c(-0.5, 0, 0.5),
311    data$width
312  ))
313  
314  y_off <- as.vector(outer(
315    c(0, 1, 0),
316    data$height
317  ))
318  
319  ang <- rep_data$angle * (pi / 180)
320  x_new <- x_off * cos(ang) - y_off * sin(ang)
321  y_new <- x_off * sin(ang) + y_off * cos(ang)
322  
323  # Origin x and y have fixed values
324  x <- unit(0.5, "npc") + unit(x_new, "cm")
325  y <- unit(0.2, "npc") + unit(y_new, "cm")
326  
327  grid::polygonGrob(
328    x = x, y = y, id = idx,
329    gp = grid::gpar(
330      col  = alpha(data$colour, data$alpha),
331      fill = alpha(data$fill, data$alpha),
332      lwd  = data$size * .pt,
333      lty  = data$linetype
334    )
335  )
336  
337}
338

When we now provide this glyph drawing function to the layer, it should draw the correct legends automatically.

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246library(ggplot2)
247
248GeomTriangles <- ggproto(
249  "GeomTriangles", GeomPoint,
250  default_aes = aes(
251    colour = "black", fill = "black", size = 0.5, linetype = 1, 
252    alpha = 1, angle = 0, width = 0.5, height = 0.5
253  ),
254  
255  draw_panel = function(
256    data, panel_params, coord, na.rm = FALSE
257  ) {
258    # Apply coordinate transform
259    df <- coord$transform(data, panel_params)
260    
261    # Repeat every row 3x
262    idx <- rep(seq_len(nrow(df)), each = 3)
263    rep_df <- df[idx, ]
264    # Calculate offsets from origin
265    x_off <- as.vector(outer(c(-0.5, 0, 0.5), df$width))
266    y_off <- as.vector(outer(c(0, 1, 0), df$height))
267    
268    # Rotate offsets
269    ang <- rep_df$angle * (pi / 180)
270    x_new <- x_off * cos(ang) - y_off * sin(ang)
271    y_new <- x_off * sin(ang) + y_off * cos(ang)
272    
273    # Combine offsets with origin
274    x <- unit(rep_df$x, "npc") + unit(x_new, "cm")
275    y <- unit(rep_df$y, "npc") + unit(y_new, "cm")
276    
277    grid::polygonGrob(
278      x = x, y = y, id = idx,
279      gp = grid::gpar(
280        col  = alpha(df$colour, df$alpha),
281        fill = alpha(df$fill, df$alpha),
282        lwd  = df$size * .pt,
283        lty  = df$linetype
284      )
285    )
286  }
287)
288geom_triangles <- function(mapping = NULL, data = NULL,
289                           position = "identity", na.rm = FALSE, show.legend = NA,
290                           inherit.aes = TRUE, ...) {
291  layer(
292    stat = "identity", geom = GeomTriangles, data = data, mapping = mapping,
293    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
294    params = list(na.rm = na.rm, ...)
295  )
296}
297ggplot(mtcars, aes(mpg, disp, height = cyl, width = wt, colour = hp, fill = hp)) +
298  geom_triangles() +
299  geom_point(colour = "black") +
300  continuous_scale("width", "wscale",  
301                   palette = scales::rescale_pal(c(0.1, 0.5))) +
302  continuous_scale("height", "hscale", 
303                   palette = scales::rescale_pal(c(0.1, 0.5)))
304draw_key_triangle <- function(data, params, size) {
305  # browser()
306  idx <- rep(seq_len(nrow(data)), each = 3)
307  rep_data <- data[idx, ]
308  
309  x_off <- as.vector(outer(
310    c(-0.5, 0, 0.5),
311    data$width
312  ))
313  
314  y_off <- as.vector(outer(
315    c(0, 1, 0),
316    data$height
317  ))
318  
319  ang <- rep_data$angle * (pi / 180)
320  x_new <- x_off * cos(ang) - y_off * sin(ang)
321  y_new <- x_off * sin(ang) + y_off * cos(ang)
322  
323  # Origin x and y have fixed values
324  x <- unit(0.5, "npc") + unit(x_new, "cm")
325  y <- unit(0.2, "npc") + unit(y_new, "cm")
326  
327  grid::polygonGrob(
328    x = x, y = y, id = idx,
329    gp = grid::gpar(
330      col  = alpha(data$colour, data$alpha),
331      fill = alpha(data$fill, data$alpha),
332      lwd  = data$size * .pt,
333      lty  = data$linetype
334    )
335  )
336  
337}
338ggplot(mtcars, aes(mpg, disp, height = cyl, width = wt, colour = hp, fill = hp)) +
339  geom_triangles(key_glyph = draw_key_triangle) +
340  geom_point(colour = "black") +
341  continuous_scale("width", "wscale",  
342                   palette = scales::rescale_pal(c(0.1, 0.5))) +
343  continuous_scale("height", "hscale", 
344                   palette = scales::rescale_pal(c(0.1, 0.5)))
345

Created on 2022-01-30 by the reprex package (v2.0.1)

The ideal place for the glyph constructor is in the ggproto class. So a final ggproto class could look like:

1library(ggplot2)
2library(magrittr)
3library(dplyr)
4library(ggrepel)
5library(tibble)
6library(cowplot)
7library(patchwork)
8
9StatTriangles <- ggproto("StatTriangles", Stat,
10  required_aes = c('x', 'y', 'z'),
11  compute_group = function(data, scales, params, width = 1, height_scale = .05, width_scale = .05, angle = 0) {
12
13    # specify default width
14    if (is.null(data$width)) data$width <- 1
15
16    # for each row of the data, create the 3 points that will make up our
17    # triangle based on the z, width, height_scale, and width_scale given.
18        triangle_df <-
19            tibble::tibble(
20                group = 1:nrow(data),
21                point1 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] - width[[i]]/2*width_scale, y[[i]]))}),
22                point2 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]] + width[[i]]/2*width_scale, y[[i]]))}),
23                point3 = lapply(1:nrow(data), function(i) {with(data, c(x[[i]], y[[i]] + z[[i]]*height_scale))})
24            )
25
26        # pivot the data into a long format so that each coordinate pair (e.g. vertex)
27        # will be its own row
28        triangle_df <- triangle_df %>% tidyr::pivot_longer(
29            cols = c(point1, point2, point3),
30            names_to = 'vertex',
31            values_to = 'coordinates'
32        )
33
34        # extract the coordinates -- this must be done rowwise because
35        # coordinates is a list where each element is a c(x,y) coordinate pair
36        triangle_df <- triangle_df %>% rowwise() %>% mutate(
37            x = coordinates[[1]],
38            y = coordinates[[2]])
39
40        # save the original x and y so we can perform rotations by the
41        # given angle with reference to (orig_x, orig_y) as the fixed point
42        # of the rotation transformation
43    triangle_df$orig_x <- rep(data$x, each = 3)
44    triangle_df$orig_y <- rep(data$y, each = 3)
45
46    # i'm not sure exactly why, but if the group isn't interacted with linetype
47    # then the edges of the triangles get messed up when rendered when linetype
48    # is used in an aesthetic
49    # triangle_df$group <-
50    #   paste0(triangle_df$orig_x, triangle_df$orig_y, triangle_df$group, rep(data$group, each = 3))
51
52        # fill in aesthetics to the dataframe
53    triangle_df$colour <- rep(data$colour, each = 3)
54    triangle_df$size <- rep(data$size, each = 3)
55    triangle_df$fill <- rep(data$fill, each = 3)
56    triangle_df$linetype <- rep(data$linetype, each = 3)
57    triangle_df$alpha <- rep(data$alpha, each = 3)
58    triangle_df$angle <- rep(data$angle, each = 3)
59
60    # determine scaling factor in going from y to x
61    # scale_factor <- diff(range(data$x)) / diff(range(data$y))
62    scale_factor <- diff(scales$x$get_limits()) / diff(scales$y$get_limits())
63    if (! is.finite(scale_factor) | is.na(scale_factor)) scale_factor <- 1
64
65    # rotate the data according to the angle by first subtracting out the
66    # (orig_x, orig_y) component, applying coordinate rotations, and then
67    # adding the (orig_x, orig_y) component back in.
68        new_coords <- triangle_df %>% mutate(
69      x_diff = x - orig_x,
70      y_diff = (y - orig_y) * scale_factor,
71      x_new = x_diff * cos(angle) - y_diff * sin(angle),
72      y_new = x_diff * sin(angle) + y_diff * cos(angle),
73      x_new = orig_x + x_new*scale_factor,
74      y_new = (orig_y + y_new)
75        )
76
77        # overwrite the x,y coordinates with the newly computed coordinates
78        triangle_df$x <- new_coords$x_new
79        triangle_df$y <- new_coords$y_new
80
81    triangle_df
82  }
83)
84
85stat_triangles <- function(mapping = NULL, data = NULL, geom = "polygon",
86                       position = "identity", na.rm = FALSE, show.legend = NA,
87                       inherit.aes = TRUE, ...) {
88  layer(
89    stat = StatTriangles, data = data, mapping = mapping, geom = geom,
90    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
91    params = list(na.rm = na.rm, ...)
92  )
93}
94
95GeomTriangles <- ggproto("GeomTriangles", GeomPolygon,
96    default_aes = aes(
97            color = 'black', fill = "black", size = 0.5, linetype = 1, alpha = 1, angle = 0, width = 1
98        )
99)
100
101geom_triangles <- function(mapping = NULL, data = NULL,
102                       position = "identity", na.rm = FALSE, show.legend = NA,
103                       inherit.aes = TRUE, ...) {
104  layer(
105    stat = StatTriangles, geom = GeomTriangles, data = data, mapping = mapping,
106    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
107    params = list(na.rm = na.rm, ...)
108  )
109}
110
111# here's an example using mtcars 
112
113plt_orig <- mtcars %>%
114  tibble::rownames_to_column('name') %>%
115  ggplot(aes(x = mpg, y = disp, z = cyl, width = wt, color = hp, fill = hp, label = name)) +
116  geom_triangles(width_scale = 10, height_scale = 15, alpha = .7) +
117  geom_point(color = 'black', size = 1) +
118  ggrepel::geom_text_repel(color = 'black', size = 2, nudge_y = -10) +
119  scale_fill_viridis_c(end = .6) +
120  scale_color_viridis_c(end = .6) +
121  xlab("miles per gallon") +
122  ylab("engine displacement (cu. in.)") +
123  labs(fill = 'horsepower', color = 'horsepower') +
124  ggtitle("MPG, Engine Displacement, # of Cylinders, Weight, and Horsepower of Cars from the 1974 Motor Trends Magazine",
125  "Cylinders shown in height, weight in width, horsepower in color") +
126  theme_bw() +
127  theme(plot.title = element_text(size = 10), plot.subtitle = element_text(size = 8), legend.title = element_text(size = 10))
128
129plt_orig
130draw_geom_triangles_height_legend <- function(
131  width = 1,
132  width_scale = .1,
133  height_scale = .1,
134  z_values = 1:3,
135  n.breaks = 3,
136  labels = c("low", "medium", "high"),
137  color = 'black',
138  fill = 'black'
139) {
140  ggplot(
141    data = data.frame(x = rep(0, times = n.breaks),
142                      y = seq(1,n.breaks),
143                      z = quantile(z_values, seq(0, 1, length.out = n.breaks)) %>% as.vector(),
144                      width = width,
145                      label = labels,
146                      color = color,
147                      fill = fill
148    ),
149    mapping = aes(x = x, y = y, z = z, label = label, width = width)
150  ) +
151    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
152    geom_text(mapping = aes(x = x + .5), size = 3) +
153    expand_limits(x = c(-.25, 3/4)) +
154    theme_void() +
155    theme(plot.title = element_text(size = 10, hjust = .5))
156}
157
158draw_geom_triangles_width_legend <- function(
159  width = 1:3,
160  width_scale = .1,
161  height_scale = .1,
162  z_values = 1,
163  n.breaks = 3,
164  labels = c("low", "medium", "high"),
165  color = 'black',
166  fill = 'black'
167) {
168  ggplot(
169    data = data.frame(x = rep(0, times = n.breaks),
170                      y = seq(1, n.breaks),
171                      z = rep(1, n.breaks),
172                      width = width,
173                      label = labels,
174                      color = color,
175                      fill = fill
176    ),
177    mapping = aes(x = x, y = y, z = z, label = label, width = width)
178  ) +
179    geom_triangles(width_scale = width_scale, height_scale = height_scale, color = color, fill = fill) +
180    geom_text(mapping = aes(x = x + .5), size = 3) +
181    expand_limits(x = c(-.25, 3/4)) +
182    theme_void() +
183    theme(plot.title = element_text(size = 10, hjust = .5))
184}
185
186# extract the original legend - this is for the color and fill (hp)
187legend_hp <- cowplot::get_legend(plt_orig)
188
189# remove the legend from the plot
190plt <- plt_orig + theme(legend.position = 'none')
191
192# create a height legend using draw_geom_triangles_height_legend
193height_legend <- 
194  draw_geom_triangles_height_legend(z_values = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl)),
195                                    labels = c(min(mtcars$cyl), median(mtcars$cyl), max(mtcars$cyl))
196                                    ) +
197                                    ggtitle("cylinders\n")
198
199
200# create a width legend using draw_geom_triangles_width_legend
201width_legend <- 
202  draw_geom_triangles_width_legend(
203  width = quantile(mtcars$wt, c(.33, .66, 1)),
204  labels = round(quantile(mtcars$wt, c(.33, .66, 1)), 2),
205  width_scale = .2
206  ) +
207  ggtitle("weight\n(1000 lbs)\n")
208
209blank_plot <- ggplot() + theme_void()
210  
211# create a legend column layout
212# 
213# whitespace is used above, below, and in-between the legend components to
214# make sure the legend column pieces don't appear too densely stacked.
215# 
216legend_component <-
217  (blank_plot /  cowplot::plot_grid(legend_hp) / blank_plot /  height_legend / blank_plot / width_legend / blank_plot) +
218  plot_layout(heights = c(1, 1, .5, 1, .5, 1, 1))
219
220# create the layout with the plot and the legend component
221(plt + legend_component) + 
222  plot_layout(nrow = 1, widths = c(1, .15))
223> sessionInfo()
224R version 4.1.2 (2021-11-01)
225Platform: x86_64-apple-darwin17.0 (64-bit)
226Running under: macOS Mojave 10.14.2
227
228Matrix products: default
229BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
230LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
231
232locale:
233[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
234
235attached base packages:
236[1] stats     graphics  grDevices utils     datasets  methods   base     
237
238other attached packages:
239[1] patchwork_1.1.1 cowplot_1.1.1   tibble_3.1.6    ggrepel_0.9.1   dplyr_1.0.7     magrittr_2.0.1  ggplot2_3.3.5   colorout_1.2-2 
240
241loaded via a namespace (and not attached):
242 [1] Rcpp_1.0.7        tidyselect_1.1.1  munsell_0.5.0     viridisLite_0.4.0 colorspace_2.0-2  R6_2.5.1          rlang_0.4.12      fansi_0.5.0      
243 [9] tools_4.1.2       grid_4.1.2        gtable_0.3.0      utf8_1.2.2        DBI_1.1.2         withr_2.4.3       ellipsis_0.3.2    digest_0.6.29    
244[17] yaml_2.2.1        assertthat_0.2.1  lifecycle_1.0.1   crayon_1.4.2      tidyr_1.1.4       farver_2.1.0      purrr_0.3.4       vctrs_0.3.8      
245[25] glue_1.6.0        labeling_0.4.2    compiler_4.1.2    pillar_1.6.4      generics_0.1.1    scales_1.1.1      pkgconfig_2.0.3  
246library(ggplot2)
247
248GeomTriangles <- ggproto(
249  "GeomTriangles", GeomPoint,
250  default_aes = aes(
251    colour = "black", fill = "black", size = 0.5, linetype = 1, 
252    alpha = 1, angle = 0, width = 0.5, height = 0.5
253  ),
254  
255  draw_panel = function(
256    data, panel_params, coord, na.rm = FALSE
257  ) {
258    # Apply coordinate transform
259    df <- coord$transform(data, panel_params)
260    
261    # Repeat every row 3x
262    idx <- rep(seq_len(nrow(df)), each = 3)
263    rep_df <- df[idx, ]
264    # Calculate offsets from origin
265    x_off <- as.vector(outer(c(-0.5, 0, 0.5), df$width))
266    y_off <- as.vector(outer(c(0, 1, 0), df$height))
267    
268    # Rotate offsets
269    ang <- rep_df$angle * (pi / 180)
270    x_new <- x_off * cos(ang) - y_off * sin(ang)
271    y_new <- x_off * sin(ang) + y_off * cos(ang)
272    
273    # Combine offsets with origin
274    x <- unit(rep_df$x, "npc") + unit(x_new, "cm")
275    y <- unit(rep_df$y, "npc") + unit(y_new, "cm")
276    
277    grid::polygonGrob(
278      x = x, y = y, id = idx,
279      gp = grid::gpar(
280        col  = alpha(df$colour, df$alpha),
281        fill = alpha(df$fill, df$alpha),
282        lwd  = df$size * .pt,
283        lty  = df$linetype
284      )
285    )
286  }
287)
288geom_triangles <- function(mapping = NULL, data = NULL,
289                           position = "identity", na.rm = FALSE, show.legend = NA,
290                           inherit.aes = TRUE, ...) {
291  layer(
292    stat = "identity", geom = GeomTriangles, data = data, mapping = mapping,
293    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
294    params = list(na.rm = na.rm, ...)
295  )
296}
297ggplot(mtcars, aes(mpg, disp, height = cyl, width = wt, colour = hp, fill = hp)) +
298  geom_triangles() +
299  geom_point(colour = "black") +
300  continuous_scale("width", "wscale",  
301                   palette = scales::rescale_pal(c(0.1, 0.5))) +
302  continuous_scale("height", "hscale", 
303                   palette = scales::rescale_pal(c(0.1, 0.5)))
304draw_key_triangle <- function(data, params, size) {
305  # browser()
306  idx <- rep(seq_len(nrow(data)), each = 3)
307  rep_data <- data[idx, ]
308  
309  x_off <- as.vector(outer(
310    c(-0.5, 0, 0.5),
311    data$width
312  ))
313  
314  y_off <- as.vector(outer(
315    c(0, 1, 0),
316    data$height
317  ))
318  
319  ang <- rep_data$angle * (pi / 180)
320  x_new <- x_off * cos(ang) - y_off * sin(ang)
321  y_new <- x_off * sin(ang) + y_off * cos(ang)
322  
323  # Origin x and y have fixed values
324  x <- unit(0.5, "npc") + unit(x_new, "cm")
325  y <- unit(0.2, "npc") + unit(y_new, "cm")
326  
327  grid::polygonGrob(
328    x = x, y = y, id = idx,
329    gp = grid::gpar(
330      col  = alpha(data$colour, data$alpha),
331      fill = alpha(data$fill, data$alpha),
332      lwd  = data$size * .pt,
333      lty  = data$linetype
334    )
335  )
336  
337}
338ggplot(mtcars, aes(mpg, disp, height = cyl, width = wt, colour = hp, fill = hp)) +
339  geom_triangles(key_glyph = draw_key_triangle) +
340  geom_point(colour = "black") +
341  continuous_scale("width", "wscale",  
342                   palette = scales::rescale_pal(c(0.1, 0.5))) +
343  continuous_scale("height", "hscale", 
344                   palette = scales::rescale_pal(c(0.1, 0.5)))
345GeomTriangles <- ggproto(
346  "GeomTriangles", GeomPoint,
347  ..., # Whatever you want to put in here
348  draw_key = draw_key_triangle
349)
350

Footnote: using scales for width and height isn't generally recommended because it may affect other geoms as well.

Source https://stackoverflow.com/questions/70916440

QUESTION

How to log production database changes made via the Django shell

Asked 2022-Jan-27 at 17:42

I would like to automatically generate some sort of log of all the database changes that are made via the Django shell in the production environment.

We use schema and data migration scripts to alter the production database and they are version controlled. Therefore if we introduce a bug, it's easy to track it back. But if a developer in the team changes the database via the Django shell which then introduces an issue, at the moment we can only hope that they remember what they did or/and we can find their commands in the Python shell history.

Example. Let's imagine that the following code was executed by a developer in the team via the Python shell:

1>>> tm = TeamMembership.objects.get(person=alice)
2>>> tm.end_date = date(2022,1,1)
3>>> tm.save()
4

It changes a team membership object in the database. I would like to log this somehow.

I'm aware that there are a bunch of Django packages related to audit logging, but I'm only interested in the changes that are triggered from the Django shell, and I want to log the Python code that updated the data.

So the questions I have in mind:

  • I can log the statements from IPython but how do I know which one touched the database?
  • I can listen to the pre_save signal for all model to know if data changes, but how do I know if the source was from the Python shell? How do I know what was the original Python statement?

ANSWER

Answered 2022-Jan-19 at 09:20

You could use django's receiver annotation.

For example, if you want to detect any call of the save method, you could do:

1>>> tm = TeamMembership.objects.get(person=alice)
2>>> tm.end_date = date(2022,1,1)
3>>> tm.save()
4from django.db.models.signals import post_save
5from django.dispatch import receiver
6import logging
7
8@receiver(post_save)
9def logg_save(sender, instance, **kwargs):
10    logging.debug("whatever you want to log")
11

Source https://stackoverflow.com/questions/70658151

QUESTION

Memoize multi-dimensional recursive solutions in haskell

Asked 2022-Jan-13 at 14:28

I was solving a recursive problem in haskell, although I could get the solution I would like to cache outputs of sub problems since has over lapping sub-problem property.

The question is, given a grid of dimension n*m, and an integer k, how many ways are there to reach the gird (n, m) from (1, 1) with not more than k change of direction?

Here is the code without of memoization

1paths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
2paths i j n m k dir
3    | i > n || j > m || k < 0 = 0
4    | i == n && j == m = 1
5    | dir == 0 = paths (i+1) j n m k 1 + paths i (j+1) n m k 2        -- is in grid (1,1)
6    | dir == 1 = paths (i+1) j n m k 1 + paths i (j+1) n m (k-1) 2    -- down was the direction took to reach here
7    | dir == 2 = paths (i+1) j n m (k-1) 1 + paths i (j+1) n m k 2    -- right was the direction took to reach here 
8    | otherwise = -1
9

Here the dependent variables are i, j, k, dir. In languages like C++/Java a 4-d DP array could have been used (dp[n][m][k][3], in Haskell I can't find a way to implement that.

ANSWER

Answered 2021-Dec-16 at 16:23

In Haskell these kinds of things aren't the most trivial ones, indeed. You would really like to have some in-place mutations going on to save up on memory and time, so I don't see any better way than equipping the frightening ST monad.

This could be done over various data structures, arrays, vectors, repa tensors. I chose HashTable from hashtables because it is the simplest to use and is performant enough to make sense in my example.


First of all, introduction:

1paths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
2paths i j n m k dir
3    | i > n || j > m || k < 0 = 0
4    | i == n && j == m = 1
5    | dir == 0 = paths (i+1) j n m k 1 + paths i (j+1) n m k 2        -- is in grid (1,1)
6    | dir == 1 = paths (i+1) j n m k 1 + paths i (j+1) n m (k-1) 2    -- down was the direction took to reach here
7    | dir == 2 = paths (i+1) j n m (k-1) 1 + paths i (j+1) n m k 2    -- right was the direction took to reach here 
8    | otherwise = -1
9{-# LANGUAGE Rank2Types #-}
10module Solution where
11
12import Control.Monad.ST
13import Control.Monad
14import Data.HashTable.ST.Basic as HT
15

Rank2Types are useful when dealing with ST, because of the phantom types. I picked the Basic variant of the hashtable, because authors claim it has the fastest lookups --- and we are going to lookup a lot.

It is advised to use a type alias for the map, so here we go:

1paths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
2paths i j n m k dir
3    | i > n || j > m || k < 0 = 0
4    | i == n && j == m = 1
5    | dir == 0 = paths (i+1) j n m k 1 + paths i (j+1) n m k 2        -- is in grid (1,1)
6    | dir == 1 = paths (i+1) j n m k 1 + paths i (j+1) n m (k-1) 2    -- down was the direction took to reach here
7    | dir == 2 = paths (i+1) j n m (k-1) 1 + paths i (j+1) n m k 2    -- right was the direction took to reach here 
8    | otherwise = -1
9{-# LANGUAGE Rank2Types #-}
10module Solution where
11
12import Control.Monad.ST
13import Control.Monad
14import Data.HashTable.ST.Basic as HT
15type Mem s = HT.HashTable s (Int, Int, Int, Int) Integer
16

ST-free entrypoint just to create the map and call our monster:

1paths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
2paths i j n m k dir
3    | i > n || j > m || k < 0 = 0
4    | i == n && j == m = 1
5    | dir == 0 = paths (i+1) j n m k 1 + paths i (j+1) n m k 2        -- is in grid (1,1)
6    | dir == 1 = paths (i+1) j n m k 1 + paths i (j+1) n m (k-1) 2    -- down was the direction took to reach here
7    | dir == 2 = paths (i+1) j n m (k-1) 1 + paths i (j+1) n m k 2    -- right was the direction took to reach here 
8    | otherwise = -1
9{-# LANGUAGE Rank2Types #-}
10module Solution where
11
12import Control.Monad.ST
13import Control.Monad
14import Data.HashTable.ST.Basic as HT
15type Mem s = HT.HashTable s (Int, Int, Int, Int) Integer
16runpaths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
17runpaths i j n m k dir = runST $ do
18  mem <- HT.new
19  paths mem i j n m k dir
20

Here is memorized computation of paths. We just try to search for the result in the map, and if it is not there then we save it and return:

1paths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
2paths i j n m k dir
3    | i > n || j > m || k < 0 = 0
4    | i == n && j == m = 1
5    | dir == 0 = paths (i+1) j n m k 1 + paths i (j+1) n m k 2        -- is in grid (1,1)
6    | dir == 1 = paths (i+1) j n m k 1 + paths i (j+1) n m (k-1) 2    -- down was the direction took to reach here
7    | dir == 2 = paths (i+1) j n m (k-1) 1 + paths i (j+1) n m k 2    -- right was the direction took to reach here 
8    | otherwise = -1
9{-# LANGUAGE Rank2Types #-}
10module Solution where
11
12import Control.Monad.ST
13import Control.Monad
14import Data.HashTable.ST.Basic as HT
15type Mem s = HT.HashTable s (Int, Int, Int, Int) Integer
16runpaths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
17runpaths i j n m k dir = runST $ do
18  mem <- HT.new
19  paths mem i j n m k dir
20mempaths mem i j n m k dir = do
21  res <- HT.lookup mem (i, j, k, dir)
22  case res of
23    Just x -> return x
24    Nothing -> do
25      x <- paths mem i j n m k dir
26      HT.insert mem (i, j, k, dir) x
27      return x
28

And here goes the brain of the algorithm. It is just a monadic action that uses calls with memorization in place of plain recursion:

1paths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
2paths i j n m k dir
3    | i > n || j > m || k < 0 = 0
4    | i == n && j == m = 1
5    | dir == 0 = paths (i+1) j n m k 1 + paths i (j+1) n m k 2        -- is in grid (1,1)
6    | dir == 1 = paths (i+1) j n m k 1 + paths i (j+1) n m (k-1) 2    -- down was the direction took to reach here
7    | dir == 2 = paths (i+1) j n m (k-1) 1 + paths i (j+1) n m k 2    -- right was the direction took to reach here 
8    | otherwise = -1
9{-# LANGUAGE Rank2Types #-}
10module Solution where
11
12import Control.Monad.ST
13import Control.Monad
14import Data.HashTable.ST.Basic as HT
15type Mem s = HT.HashTable s (Int, Int, Int, Int) Integer
16runpaths :: Int -> Int -> Int -> Int -> Int -> Int -> Integer
17runpaths i j n m k dir = runST $ do
18  mem <- HT.new
19  paths mem i j n m k dir
20mempaths mem i j n m k dir = do
21  res <- HT.lookup mem (i, j, k, dir)
22  case res of
23    Just x -> return x
24    Nothing -> do
25      x <- paths mem i j n m k dir
26      HT.insert mem (i, j, k, dir) x
27      return x
28paths mem i j n m k dir
29    | i > n || j > m || k < 0 = return 0
30    | i == n && j == m = return 1
31    | dir == 0 = do
32        x1 <- mempaths mem (i+1) j n m k 1
33        x2 <- mempaths mem i (j+1) n m k 2        -- is in grid (1,1)
34        return $ x1 + x2
35    | dir == 1 = do 
36        x1 <- mempaths mem (i+1) j n m k 1
37        x2 <- mempaths mem i (j+1) n m (k-1) 2    -- down was the direction took to reach here
38        return $ x1 + x2
39    | dir == 2 = do
40        x1 <- mempaths mem (i+1) j n m (k-1) 1 
41        x2 <- mempaths mem i (j+1) n m k 2    -- right was the direction took to reach here 
42        return $ x1 + x2
43    | otherwise = return (-1)
44

Source https://stackoverflow.com/questions/70376569

QUESTION

Is it possible to combine a ggplot legend and table

Asked 2022-Jan-07 at 03:57

I was wondering if anyone knows a way to combine a table and ggplot legend so that the legend appears as a column in the table as shown in the image. Sorry if this has been asked before but I haven't been able to find a way to do this.

Desired output

Edit: attached is code to produce the output below (minus the legend/table combination, which I am trying to produce, as I stitched that together in Powerpoint)

1library(ggplot2)
2library(gridExtra)
3library(dplyr)
4library(formattable)
5library(signal)
6
7#dataset for ggplot
8full.data <- structure(list(error = c(0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 
95, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 
105, 6, 0, 1, 2, 3, 4, 5, 6), prob.ed.n = c(0, 0, 0.2, 0.5, 0.8, 
111, 1, 0, 0, 0.3, 0.7, 1, 1, 1, 0, 0.1, 0.4, 0.9, 1, 1, 1, 0, 
120.1, 0.5, 0.9, 1, 1, 1, 0, 0.1, 0.6, 1, 1, 1, 1, 0, 0.1, 0.6, 
131, 1, 1, 1), N = c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 
143, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 
156, 6, 6, 6, 6, 6, 6)), row.names = c(NA, -42L), class = "data.frame")
16
17#summary table 
18summary.table <- structure(list(prob.fr = c("1.62%", "1.35%", "1.09%", "0.81%", "0.54%", "0.27%"), prob.ed.n = c("87.4%", "82.2%", "74.8%", "64.4%", "49.8%", "29.2%"), N = c(6, 5, 4, 3, 2, 1)), row.names = c(NA, 
19-6L), class = "data.frame")
20
21#table object to beincluded with ggplot
22table <- tableGrob(summary.table %>%
23            rename(
24              `Prb FR` = prob.fr,
25              `Prb ED` = prob.ed.n,
26            ), 
27          rows = NULL)
28#plot
29plot <- ggplot(full.data, aes(x = error, y = prob.ed.n, group = N, colour = as.factor(N))) +
30  geom_vline(xintercept = 2.45, colour = "red", linetype = "dashed") +
31  geom_hline(yintercept = 0.9, linetype = "dashed") +
32  geom_line(data = full.data %>%
33              group_by(N) %>%
34              do({
35                tibble(error = seq(min(.$error), max(.$error),length.out=100),
36                       prob.ed.n = pchip(.$error, .$prob.ed.n, error))
37              }),
38            size = 1) +
39  scale_x_continuous(labels = full.data$error, breaks = full.data$error, expand = c(0, 0.05)) +
40  scale_y_continuous(expand = expansion(add = c(0.01, 0.01))) +
41  scale_color_brewer(palette = "Dark2") +
42  guides(color = guide_legend(reverse=TRUE, nrow = 1)) +
43  theme_bw() +
44  theme(legend.key = element_rect(fill = "white", colour = "black"),
45        legend.direction= "horizontal",
46        legend.position=c(0.8,0.05)
47)
48
49#arrange plot and grid side-by-side
50grid.arrange(plot, table, nrow = 1, widths = c(4,1))
51

ANSWER

Answered 2021-Dec-31 at 13:24

This is an interesting problem. The short answer: Yes, it's possible. But I don't see a way around hard coding the position of table and legend, which is ugly.

The suggestion below requires hard coding in three places. I am using {ggpubr} for the table, and {cowplot} for the stitching.

Another problem arises from the legend key spacing for vertical legends. This is still a rather unresolved issue for other keys than polygons, to my knowledge. The associated GitHub issue is closed The legend spacing is not a problem any more. Ask teunbrand, and he knows the answer.

Some other relevant comments in the code.

1library(ggplot2)
2library(gridExtra)
3library(dplyr)
4library(formattable)
5library(signal)
6
7#dataset for ggplot
8full.data <- structure(list(error = c(0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 
95, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 
105, 6, 0, 1, 2, 3, 4, 5, 6), prob.ed.n = c(0, 0, 0.2, 0.5, 0.8, 
111, 1, 0, 0, 0.3, 0.7, 1, 1, 1, 0, 0.1, 0.4, 0.9, 1, 1, 1, 0, 
120.1, 0.5, 0.9, 1, 1, 1, 0, 0.1, 0.6, 1, 1, 1, 1, 0, 0.1, 0.6, 
131, 1, 1, 1), N = c(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 
143, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 
156, 6, 6, 6, 6, 6, 6)), row.names = c(NA, -42L), class = "data.frame")
16
17#summary table 
18summary.table <- structure(list(prob.fr = c("1.62%", "1.35%", "1.09%", "0.81%", "0.54%", "0.27%"), prob.ed.n = c("87.4%", "82.2%", "74.8%", "64.4%", "49.8%", "29.2%"), N = c(6, 5, 4, 3, 2, 1)), row.names = c(NA, 
19-6L), class = "data.frame")
20
21#table object to beincluded with ggplot
22table <- tableGrob(summary.table %>%
23            rename(
24              `Prb FR` = prob.fr,
25              `Prb ED` = prob.ed.n,
26            ), 
27          rows = NULL)
28#plot
29plot <- ggplot(full.data, aes(x = error, y = prob.ed.n, group = N, colour = as.factor(N))) +
30  geom_vline(xintercept = 2.45, colour = "red", linetype = "dashed") +
31  geom_hline(yintercept = 0.9, linetype = "dashed") +
32  geom_line(data = full.data %>%
33              group_by(N) %>%
34              do({
35                tibble(error = seq(min(.$error), max(.$error),length.out=100),
36                       prob.ed.n = pchip(.$error, .$prob.ed.n, error))
37              }),
38            size = 1) +
39  scale_x_continuous(labels = full.data$error, breaks = full.data$error, expand = c(0, 0.05)) +
40  scale_y_continuous(expand = expansion(add = c(0.01, 0.01))) +
41  scale_color_brewer(palette = "Dark2") +
42  guides(color = guide_legend(reverse=TRUE, nrow = 1)) +
43  theme_bw() +
44  theme(legend.key = element_rect(fill = "white", colour = "black"),
45        legend.direction= "horizontal",
46        legend.position=c(0.8,0.05)
47)
48
49#arrange plot and grid side-by-side
50grid.arrange(plot, table, nrow = 1, widths = c(4,1))
51library(tidyverse)
52library(ggpubr)
53library(cowplot)
54#> 
55#> Attaching package: 'cowplot'
56#> The following object is masked from 'package:ggpubr':
57#> 
58#>     get_legend
59
60full.data <- structure(list(error = c(
61  0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,
62  5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,
63  5, 6, 0, 1, 2, 3, 4, 5, 6
64), prob.ed.n = c(
65  0, 0, 0.2, 0.5, 0.8,
66  1, 1, 0, 0, 0.3, 0.7, 1, 1, 1, 0, 0.1, 0.4, 0.9, 1, 1, 1, 0,
67  0.1, 0.5, 0.9, 1, 1, 1, 0, 0.1, 0.6, 1, 1, 1, 1, 0, 0.1, 0.6,
68  1, 1, 1, 1
69), N = c(
70  1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
71  3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5,
72  6, 6, 6, 6, 6, 6, 6
73)), row.names = c(NA, -42L), class = "data.frame")
74
75summary.table <-
76  structure(list(
77    prob.fr = c("1.62%", "1.35%", "1.09%", "0.81%", "0.54%", "0.27%"),
78    prob.ed.n = c("87.4%", "82.2%", "74.8%", "64.4%", "49.8%", "29.2%"),
79    N = c(6, 5, 4, 3, 2, 1)
80  ), row.names = c(NA, -6L), class = "data.frame")
81
82## Hack 1 - create some space for the new legend
83spacer <- paste(rep(" ", 6), collapse = "")
84my_table <-
85  summary.table %>%
86  mutate(N = paste(spacer, N))
87
88p1 <-
89  ggplot(full.data, aes(x = error, y = prob.ed.n, group = N, colour = as.factor(N))) +
90  geom_vline(xintercept = 2.45, colour = "red", linetype = "dashed") +
91  geom_hline(yintercept = 0.9, linetype = "dashed") +
92  geom_line(
93    data = full.data %>%
94      group_by(N) %>%
95      do({
96        tibble(
97          error = seq(min(.$error), max(.$error), length.out = 100),
98          prob.ed.n = signal::pchip(.$error, .$prob.ed.n, error)
99        )
100      }),
101    size = 1
102  ) +
103  ## remove the legend labels. You have them in the table already.
104  scale_color_brewer(NULL, palette = "Dark2", labels = rep("", length(unique(full.data$N)))) +
105  ## remove all the legend specs! I've also removed the not so important reverse scale
106  ## I have removed fill and color to make it aesthetically more pleasing
107  theme(
108    legend.key = element_rect(fill = NA, colour = NA),
109    ## hack 2 - hard code legend key spacing
110    legend.spacing.y = unit(1.8, "pt"),
111    legend.background = element_blank()
112  ) +
113  ## make y spacing work
114  guides(color = guide_legend(byrow = TRUE))
115
116## create the plot elements
117p_leg <- cowplot::get_legend(p1)
118p2 <- ggtexttable(my_table, rows = NULL)
119## we don't want the legend twice
120p <- p1 + theme(legend.position = "none")
121
122## hack 3 - hard code the plot element positions
123ggdraw(p, xlim = c(0, 1.7)) +
124  draw_plot(p2, x = .8) +
125  draw_plot(p_leg, x = .97, y = 0.975, vjust = 1)
126

Created on 2021-12-31 by the reprex package (v2.0.1)

Source https://stackoverflow.com/questions/70511777

QUESTION

Centre CSS Grid Items Dependent On Dynamic Content Count

Asked 2021-Dec-28 at 19:40

I have a series of images that are fetched from a database, and when three or more images are added it visually shows the three columns.

When less than three images are present, because I'm using display: grid; it is currently justified to the left of the parent container (in the code example I've just used red boxes to represent the images).

Is there anyway of having it so that when one or two images are present these are justified to the centre of the parent element. I appreciate I could use javascript to detect how many images are present and if it is less than three, add a class and change the wrapper to display: flex, but I wondered if such a layout was possible with CSS only?

Due to the nature of the layout I do need to use CSS Grid when more than three images are present.

Note: I've commented out two of the red boxes in the HTML to show the initial issue when only one red box is present.

Codepen: https://codepen.io/anna_paul/pen/xxXrVJQ

1body {
2  display: flex;
3  justify-content: center;
4  margin: 0
5  width: 100%;
6  height: 100vh;
7}
8
9.wrapper {
10  display: grid;
11  grid-template-columns: 1fr 1fr 1fr;
12  grid-gap: 1rem;
13  max-width: 1250px;
14}
15
16.box {
17  width: 200px;
18  height: 200px;
19  background: red;
20}
1body {
2  display: flex;
3  justify-content: center;
4  margin: 0
5  width: 100%;
6  height: 100vh;
7}
8
9.wrapper {
10  display: grid;
11  grid-template-columns: 1fr 1fr 1fr;
12  grid-gap: 1rem;
13  max-width: 1250px;
14}
15
16.box {
17  width: 200px;
18  height: 200px;
19  background: red;
20}<div class="wrapper">
21  <div class="box"></div>
22<!--   <div class="box"></div>
23  <div class="box"></div> -->
24</div>

ANSWER

Answered 2021-Dec-20 at 07:20

using auto instead of fr and using align-content solve your problem.

1body {
2  display: flex;
3  justify-content: center;
4  margin: 0
5  width: 100%;
6  height: 100vh;
7}
8
9.wrapper {
10  display: grid;
11  grid-template-columns: 1fr 1fr 1fr;
12  grid-gap: 1rem;
13  max-width: 1250px;
14}
15
16.box {
17  width: 200px;
18  height: 200px;
19  background: red;
20}<div class="wrapper">
21  <div class="box"></div>
22<!--   <div class="box"></div>
23  <div class="box"></div> -->
24</div>body {
25  display: flex;
26  justify-content: center;
27  margin: 0
28  width: 100%;
29  height: 100vh;
30}
31
32.wrapper {
33  display: grid;
34  grid-template-columns: auto auto auto;
35  align-content : start;
36  /*  grid-gap: 1rem;  */ 
37  max-width: 1250px;
38}
39
40.box {
41  width: 200px;
42  height: 200px;
43  background: red;
44  margin: 1rem ; 
45}
1body {
2  display: flex;
3  justify-content: center;
4  margin: 0
5  width: 100%;
6  height: 100vh;
7}
8
9.wrapper {
10  display: grid;
11  grid-template-columns: 1fr 1fr 1fr;
12  grid-gap: 1rem;
13  max-width: 1250px;
14}
15
16.box {
17  width: 200px;
18  height: 200px;
19  background: red;
20}<div class="wrapper">
21  <div class="box"></div>
22<!--   <div class="box"></div>
23  <div class="box"></div> -->
24</div>body {
25  display: flex;
26  justify-content: center;
27  margin: 0
28  width: 100%;
29  height: 100vh;
30}
31
32.wrapper {
33  display: grid;
34  grid-template-columns: auto auto auto;
35  align-content : start;
36  /*  grid-gap: 1rem;  */ 
37  max-width: 1250px;
38}
39
40.box {
41  width: 200px;
42  height: 200px;
43  background: red;
44  margin: 1rem ; 
45}<div class="wrapper">
46  <div class="box"></div>
47<!--   <div class="box"></div>
48  <div class="box"></div> -->
49</div>

Source https://stackoverflow.com/questions/70408723

QUESTION

Using cowplot in R to make a ggplot chart occupy two consecutive rows

Asked 2021-Dec-21 at 18:44

This is my code:

1library(ggplot2)
2library(cowplot)
3
4
5df <- data.frame(
6  x = 1:10, y1 = 1:10, y2 = (1:10)^2, y3 = (1:10)^3, y4 = (1:10)^4
7)
8
9p1 <- ggplot(df, aes(x, y1)) + geom_point()
10p2 <- ggplot(df, aes(x, y2)) + geom_point()
11p3 <- ggplot(df, aes(x, y3)) + geom_point()
12p4 <- ggplot(df, aes(x, y4)) + geom_point()
13p5 <- ggplot(df, aes(x, y3)) + geom_point()
14# simple grid
15plot_grid(p1, p2, 
16          p3, p4,
17          p5, p4)
18

But I don't want to repeat p4 I want to "stretch" p4 to occupy col2 and rows 2 and 3.

Any help?

ANSWER

Answered 2021-Dec-21 at 00:17

You may find this easier using gridExtra::grid.arrange().

1library(ggplot2)
2library(cowplot)
3
4
5df <- data.frame(
6  x = 1:10, y1 = 1:10, y2 = (1:10)^2, y3 = (1:10)^3, y4 = (1:10)^4
7)
8
9p1 <- ggplot(df, aes(x, y1)) + geom_point()
10p2 <- ggplot(df, aes(x, y2)) + geom_point()
11p3 <- ggplot(df, aes(x, y3)) + geom_point()
12p4 <- ggplot(df, aes(x, y4)) + geom_point()
13p5 <- ggplot(df, aes(x, y3)) + geom_point()
14# simple grid
15plot_grid(p1, p2, 
16          p3, p4,
17          p5, p4)
18library(gridExtra)
19
20grid.arrange(p1, p2, p3, p4, p5, 
21             ncol = 2, 
22             layout_matrix = cbind(c(1,3,5), c(2,4,4)))
23

Result:

enter image description here

Source https://stackoverflow.com/questions/70429294

QUESTION

Stopping CSS Grid column from overflowing

Asked 2021-Dec-18 at 21:12

I tried stopping the column overflow with max-height, max-width, but it doesn't seem to work.

I've made three columns with CSS Grid. One for the nav section, one for the left column and one for the right column. the left column section keeps overflowing over the nav section and the right column section as shown in the screenshots.

What I'm trying to achieve:

enter image description here

What happens:

enter image description here

1@import url("https://fonts.googleapis.com/css2?family=Asap:wght@400;700&display=swap");
2* {
3  padding: 0;
4  margin: 0;
5  height: 100%;
6  width: 100%;
7  background-color: #4a6163;
8  font-family: "Asap";
9  -webkit-box-sizing: border-box;
10          box-sizing: border-box;
11}
12
13.main_grid {
14  display: -ms-grid;
15  display: grid;
16  -ms-grid-columns: 0.25fr (1fr)[2];
17      grid-template-columns: 0.25fr repeat(2, 1fr);
18  -ms-grid-rows: 1fr;
19      grid-template-rows: 1fr;
20  grid-column-gap: 0px;
21  grid-row-gap: 0px;
22  max-height: 100%;
23  max-width: 100%;
24}
25
26.nav_section {
27  -ms-grid-row: 1;
28  -ms-grid-column: 1;
29  -ms-grid-column-span: 1;
30  grid-area: 1 / 1 / 1 / 2;
31  border: 3px yellow solid;
32}
33
34.left_column {
35  -ms-grid-row: 1;
36  -ms-grid-column: 2;
37  -ms-grid-column-span: 1;
38  grid-area: 1 / 2 / 1 / 3;
39  border: 1px yellow solid;
40}
41
42.right_colomn {
43  -ms-grid-row: 1;
44  -ms-grid-column: 3;
45  -ms-grid-column-span: 1;
46  grid-area: 1 / 3 / 1 / 4;
47  border: 2px blue solid;
48}
49
50.left_column > h1 {
51  font-family: "Asap";
52  color: #f9faf4;
53  font-size: 13rem;
54  font-style: normal;
55  font-weight: normal;
56  line-height: 15.75rem;
57  text-transform: uppercase;
58  -webkit-transform: rotate(-90deg);
59          transform: rotate(-90deg);
60  display: -webkit-box;
61  display: -ms-flexbox;
62  display: flex;
63  border: red 3px solid;
64  -o-object-fit: contain;
65     object-fit: contain;
66  max-width: 100%;
67  max-height: 100%;
68}
69
70.main_bio {
71  color: #f2c4ce;
72  font-size: 1.75rem;
73  text-decoration: underline;
74}
1@import url("https://fonts.googleapis.com/css2?family=Asap:wght@400;700&display=swap");
2* {
3  padding: 0;
4  margin: 0;
5  height: 100%;
6  width: 100%;
7  background-color: #4a6163;
8  font-family: "Asap";
9  -webkit-box-sizing: border-box;
10          box-sizing: border-box;
11}
12
13.main_grid {
14  display: -ms-grid;
15  display: grid;
16  -ms-grid-columns: 0.25fr (1fr)[2];
17      grid-template-columns: 0.25fr repeat(2, 1fr);
18  -ms-grid-rows: 1fr;
19      grid-template-rows: 1fr;
20  grid-column-gap: 0px;
21  grid-row-gap: 0px;
22  max-height: 100%;
23  max-width: 100%;
24}
25
26.nav_section {
27  -ms-grid-row: 1;
28  -ms-grid-column: 1;
29  -ms-grid-column-span: 1;
30  grid-area: 1 / 1 / 1 / 2;
31  border: 3px yellow solid;
32}
33
34.left_column {
35  -ms-grid-row: 1;
36  -ms-grid-column: 2;
37  -ms-grid-column-span: 1;
38  grid-area: 1 / 2 / 1 / 3;
39  border: 1px yellow solid;
40}
41
42.right_colomn {
43  -ms-grid-row: 1;
44  -ms-grid-column: 3;
45  -ms-grid-column-span: 1;
46  grid-area: 1 / 3 / 1 / 4;
47  border: 2px blue solid;
48}
49
50.left_column > h1 {
51  font-family: "Asap";
52  color: #f9faf4;
53  font-size: 13rem;
54  font-style: normal;
55  font-weight: normal;
56  line-height: 15.75rem;
57  text-transform: uppercase;
58  -webkit-transform: rotate(-90deg);
59          transform: rotate(-90deg);
60  display: -webkit-box;
61  display: -ms-flexbox;
62  display: flex;
63  border: red 3px solid;
64  -o-object-fit: contain;
65     object-fit: contain;
66  max-width: 100%;
67  max-height: 100%;
68}
69
70.main_bio {
71  color: #f2c4ce;
72  font-size: 1.75rem;
73  text-decoration: underline;
74}    <main>
75        <div class="main_grid">
76            <div class="nav_section">
77                <nav class="main_nav">
78                    <a href="#">home</a>
79                    <a href="#">work</a>
80                    <a href="#">contact</a>
81                </nav>
82            </div>
83            <div class="left_column">
84                <h1 class="main_title">Hello, I'm Jack</h1>
85            </div>
86           <div class="right_colomn">
87              <p class="main_bio">A 20 YEAR OLD FROM A SMALL TOWN NEAR AMSTERDAM. CURRENTLY STUDYING COMPUTER SCIENCE IN LEIDEN.</p>
88           </div>
89        </div>
90    </main>

ANSWER

Answered 2021-Dec-18 at 21:12

To avoid overflowing, you can use the rule white-space: nowrap; for your h1. However, that will avoid breaking the line after "Hello," as well.

So I would also recommend adding a <br /> after the Hello, for explicitly breaking that line.

That should solve your line-break issues, but I noticed you're also rotating the text by 90deg, and that can mess up the heading fitting inside the cell.

So I recommend adding the rule writing-mode: tb-rl (link) to make the text be written vertically, and then rotating it 180deg instead of 90 (so it becomes bottom-up instead of top-down)

This is your snippet with the suggested changes

1@import url("https://fonts.googleapis.com/css2?family=Asap:wght@400;700&amp;display=swap");
2* {
3  padding: 0;
4  margin: 0;
5  height: 100%;
6  width: 100%;
7  background-color: #4a6163;
8  font-family: "Asap";
9  -webkit-box-sizing: border-box;
10          box-sizing: border-box;
11}
12
13.main_grid {
14  display: -ms-grid;
15  display: grid;
16  -ms-grid-columns: 0.25fr (1fr)[2];
17      grid-template-columns: 0.25fr repeat(2, 1fr);
18  -ms-grid-rows: 1fr;
19      grid-template-rows: 1fr;
20  grid-column-gap: 0px;
21  grid-row-gap: 0px;
22  max-height: 100%;
23  max-width: 100%;
24}
25
26.nav_section {
27  -ms-grid-row: 1;
28  -ms-grid-column: 1;
29  -ms-grid-column-span: 1;
30  grid-area: 1 / 1 / 1 / 2;
31  border: 3px yellow solid;
32}
33
34.left_column {
35  -ms-grid-row: 1;
36  -ms-grid-column: 2;
37  -ms-grid-column-span: 1;
38  grid-area: 1 / 2 / 1 / 3;
39  border: 1px yellow solid;
40}
41
42.right_colomn {
43  -ms-grid-row: 1;
44  -ms-grid-column: 3;
45  -ms-grid-column-span: 1;
46  grid-area: 1 / 3 / 1 / 4;
47  border: 2px blue solid;
48}
49
50.left_column &gt; h1 {
51  font-family: "Asap";
52  color: #f9faf4;
53  font-size: 13rem;
54  font-style: normal;
55  font-weight: normal;
56  line-height: 15.75rem;
57  text-transform: uppercase;
58  -webkit-transform: rotate(-90deg);
59          transform: rotate(-90deg);
60  display: -webkit-box;
61  display: -ms-flexbox;
62  display: flex;
63  border: red 3px solid;
64  -o-object-fit: contain;
65     object-fit: contain;
66  max-width: 100%;
67  max-height: 100%;
68}
69
70.main_bio {
71  color: #f2c4ce;
72  font-size: 1.75rem;
73  text-decoration: underline;
74}    &lt;main&gt;
75        &lt;div class="main_grid"&gt;
76            &lt;div class="nav_section"&gt;
77                &lt;nav class="main_nav"&gt;
78                    &lt;a href="#"&gt;home&lt;/a&gt;
79                    &lt;a href="#"&gt;work&lt;/a&gt;
80                    &lt;a href="#"&gt;contact&lt;/a&gt;
81                &lt;/nav&gt;
82            &lt;/div&gt;
83            &lt;div class="left_column"&gt;
84                &lt;h1 class="main_title"&gt;Hello, I'm Jack&lt;/h1&gt;
85            &lt;/div&gt;
86           &lt;div class="right_colomn"&gt;
87              &lt;p class="main_bio"&gt;A 20 YEAR OLD FROM A SMALL TOWN NEAR AMSTERDAM. CURRENTLY STUDYING COMPUTER SCIENCE IN LEIDEN.&lt;/p&gt;
88           &lt;/div&gt;
89        &lt;/div&gt;
90    &lt;/main&gt;@import url("https://fonts.googleapis.com/css2?family=Asap:wght@400;700&amp;display=swap");
91* {
92  padding: 0;
93  margin: 0;
94  height: 100%;
95  width: 100%;
96  background-color: #4a6163;
97  font-family: "Asap";
98  -webkit-box-sizing: border-box;
99          box-sizing: border-box;
100}
101
102.main_grid {
103  display: -ms-grid;
104  display: grid;
105  -ms-grid-columns: 0.25fr (1fr)[2];
106      grid-template-columns: 0.25fr repeat(2, 1fr);
107  -ms-grid-rows: 1fr;
108      grid-template-rows: 1fr;
109  grid-column-gap: 0px;
110  grid-row-gap: 0px;
111  max-height: 100%;
112  max-width: 100%;
113}
114
115.nav_section {
116  -ms-grid-row: 1;
117  -ms-grid-column: 1;
118  -ms-grid-column-span: 1;
119  grid-area: 1 / 1 / 1 / 2;
120  border: 3px yellow solid;
121}
122
123.left_column {
124  -ms-grid-row: 1;
125  -ms-grid-column: 2;
126  -ms-grid-column-span: 1;
127  grid-area: 1 / 2 / 1 / 3;
128  border: 1px yellow solid;
129}
130
131.right_colomn {
132  -ms-grid-row: 1;
133  -ms-grid-column: 3;
134  -ms-grid-column-span: 1;
135  grid-area: 1 / 3 / 1 / 4;
136  border: 2px blue solid;
137}
138
139.left_column &gt; h1 {
140  font-family: "Asap";
141  color: #f9faf4;
142  font-size: 13rem;
143  font-style: normal;
144  font-weight: normal;
145  line-height: 15.75rem;
146  text-transform: uppercase;
147  /* Updated the following 3 lines */
148  white-space: nowrap;
149  writing-mode: tb-rl;
150  -webkit-transform: rotate(-180deg);
151          transform: rotate(-180deg);
152  display: -webkit-box;
153  display: -ms-flexbox;
154  display: flex;
155  border: red 3px solid;
156  -o-object-fit: contain;
157     object-fit: contain;
158  max-width: 100%;
159  max-height: 100%;
160}
161
162.main_bio {
163  color: #f2c4ce;
164  font-size: 1.75rem;
165  text-decoration: underline;
166}
1@import url("https://fonts.googleapis.com/css2?family=Asap:wght@400;700&amp;display=swap");
2* {
3  padding: 0;
4  margin: 0;
5  height: 100%;
6  width: 100%;
7  background-color: #4a6163;
8  font-family: "Asap";
9  -webkit-box-sizing: border-box;
10          box-sizing: border-box;
11}
12
13.main_grid {
14  display: -ms-grid;
15  display: grid;
16  -ms-grid-columns: 0.25fr (1fr)[2];
17      grid-template-columns: 0.25fr repeat(2, 1fr);
18  -ms-grid-rows: 1fr;
19      grid-template-rows: 1fr;
20  grid-column-gap: 0px;
21  grid-row-gap: 0px;
22  max-height: 100%;
23  max-width: 100%;
24}
25
26.nav_section {
27  -ms-grid-row: 1;
28  -ms-grid-column: 1;
29  -ms-grid-column-span: 1;
30  grid-area: 1 / 1 / 1 / 2;
31  border: 3px yellow solid;
32}
33
34.left_column {
35  -ms-grid-row: 1;
36  -ms-grid-column: 2;
37  -ms-grid-column-span: 1;
38  grid-area: 1 / 2 / 1 / 3;
39  border: 1px yellow solid;
40}
41
42.right_colomn {
43  -ms-grid-row: 1;
44  -ms-grid-column: 3;
45  -ms-grid-column-span: 1;
46  grid-area: 1 / 3 / 1 / 4;
47  border: 2px blue solid;
48}
49
50.left_column &gt; h1 {
51  font-family: "Asap";
52  color: #f9faf4;
53  font-size: 13rem;
54  font-style: normal;
55  font-weight: normal;
56  line-height: 15.75rem;
57  text-transform: uppercase;
58  -webkit-transform: rotate(-90deg);
59          transform: rotate(-90deg);
60  display: -webkit-box;
61  display: -ms-flexbox;
62  display: flex;
63  border: red 3px solid;
64  -o-object-fit: contain;
65     object-fit: contain;
66  max-width: 100%;
67  max-height: 100%;
68}
69
70.main_bio {
71  color: #f2c4ce;
72  font-size: 1.75rem;
73  text-decoration: underline;
74}    &lt;main&gt;
75        &lt;div class="main_grid"&gt;
76            &lt;div class="nav_section"&gt;
77                &lt;nav class="main_nav"&gt;
78                    &lt;a href="#"&gt;home&lt;/a&gt;
79                    &lt;a href="#"&gt;work&lt;/a&gt;
80                    &lt;a href="#"&gt;contact&lt;/a&gt;
81                &lt;/nav&gt;
82            &lt;/div&gt;
83            &lt;div class="left_column"&gt;
84                &lt;h1 class="main_title"&gt;Hello, I'm Jack&lt;/h1&gt;
85            &lt;/div&gt;
86           &lt;div class="right_colomn"&gt;
87              &lt;p class="main_bio"&gt;A 20 YEAR OLD FROM A SMALL TOWN NEAR AMSTERDAM. CURRENTLY STUDYING COMPUTER SCIENCE IN LEIDEN.&lt;/p&gt;
88           &lt;/div&gt;
89        &lt;/div&gt;
90    &lt;/main&gt;@import url("https://fonts.googleapis.com/css2?family=Asap:wght@400;700&amp;display=swap");
91* {
92  padding: 0;
93  margin: 0;
94  height: 100%;
95  width: 100%;
96  background-color: #4a6163;
97  font-family: "Asap";
98  -webkit-box-sizing: border-box;
99          box-sizing: border-box;
100}
101
102.main_grid {
103  display: -ms-grid;
104  display: grid;
105  -ms-grid-columns: 0.25fr (1fr)[2];
106      grid-template-columns: 0.25fr repeat(2, 1fr);
107  -ms-grid-rows: 1fr;
108      grid-template-rows: 1fr;
109  grid-column-gap: 0px;
110  grid-row-gap: 0px;
111  max-height: 100%;
112  max-width: 100%;
113}
114
115.nav_section {
116  -ms-grid-row: 1;
117  -ms-grid-column: 1;
118  -ms-grid-column-span: 1;
119  grid-area: 1 / 1 / 1 / 2;
120  border: 3px yellow solid;
121}
122
123.left_column {
124  -ms-grid-row: 1;
125  -ms-grid-column: 2;
126  -ms-grid-column-span: 1;
127  grid-area: 1 / 2 / 1 / 3;
128  border: 1px yellow solid;
129}
130
131.right_colomn {
132  -ms-grid-row: 1;
133  -ms-grid-column: 3;
134  -ms-grid-column-span: 1;
135  grid-area: 1 / 3 / 1 / 4;
136  border: 2px blue solid;
137}
138
139.left_column &gt; h1 {
140  font-family: "Asap";
141  color: #f9faf4;
142  font-size: 13rem;
143  font-style: normal;
144  font-weight: normal;
145  line-height: 15.75rem;
146  text-transform: uppercase;
147  /* Updated the following 3 lines */
148  white-space: nowrap;
149  writing-mode: tb-rl;
150  -webkit-transform: rotate(-180deg);
151          transform: rotate(-180deg);
152  display: -webkit-box;
153  display: -ms-flexbox;
154  display: flex;
155  border: red 3px solid;
156  -o-object-fit: contain;
157     object-fit: contain;
158  max-width: 100%;
159  max-height: 100%;
160}
161
162.main_bio {
163  color: #f2c4ce;
164  font-size: 1.75rem;
165  text-decoration: underline;
166}    &lt;main&gt;
167        &lt;div class="main_grid"&gt;
168            &lt;div class="nav_section"&gt;
169                &lt;nav class="main_nav"&gt;
170                    &lt;a href="#"&gt;home&lt;/a&gt;
171                    &lt;a href="#"&gt;work&lt;/a&gt;
172                    &lt;a href="#"&gt;contact&lt;/a&gt;
173                &lt;/nav&gt;
174            &lt;/div&gt;
175            &lt;div class="left_column"&gt;
176                &lt;h1 class="main_title"&gt;Hello,&lt;br/&gt;I'm Jack&lt;/h1&gt;
177            &lt;/div&gt;
178           &lt;div class="right_colomn"&gt;
179              &lt;p class="main_bio"&gt;A 20 YEAR OLD FROM A SMALL TOWN NEAR AMSTERDAM. CURRENTLY STUDYING COMPUTER SCIENCE IN LEIDEN.&lt;/p&gt;
180           &lt;/div&gt;
181        &lt;/div&gt;
182    &lt;/main&gt;

Source https://stackoverflow.com/questions/70406683

QUESTION

logistic regression and GridSearchCV using python sklearn

Asked 2021-Dec-10 at 14:14

I am trying code from this page. I ran up to the part LR (tf-idf) and got the similar results

After that I decided to try GridSearchCV. My questions below:

1)

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16

Then I calculated f1 score manually. why it is not matching?

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]
17final_prediction=np.where(logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]&gt;=0.5,1,0)
18#https://www.statology.org/f1-score-in-python/
19from sklearn.metrics import f1_score
20#calculate F1 score
21f1_score(y_train, final_prediction)
220.9839388145315489
23
  1. If I try scoring='precision' why does it give below error? I am not clear mainly because I have relatively balanced dataset (55-45%) and f1 which requires precision is getting calculated without any problems

#lets try gridsearchcv #https://www.kaggle.com/enespolat/grid-search-with-logistic-regression

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]
17final_prediction=np.where(logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]&gt;=0.5,1,0)
18#https://www.statology.org/f1-score-in-python/
19from sklearn.metrics import f1_score
20#calculate F1 score
21f1_score(y_train, final_prediction)
220.9839388145315489
23from sklearn.model_selection import GridSearchCV
24
25grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
26logreg=LogisticRegression(solver = 'liblinear')
27logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='precision')
28logreg_cv.fit(X_train_vectors_tfidf, y_train)
29
30print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
31print(&quot;best score :&quot;,logreg_cv.best_score_)
32
33
34
35/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
36  _warn_prf(average, modifier, msg_start, len(result))
37/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
38  _warn_prf(average, modifier, msg_start, len(result))
39/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
40  _warn_prf(average, modifier, msg_start, len(result))
41/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
42  _warn_prf(average, modifier, msg_start, len(result))
43/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
44  _warn_prf(average, modifier, msg_start, len(result))
45/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
46  _warn_prf(average, modifier, msg_start, len(result))
47tuned hpyerparameters :(best parameters)  {'C': 0.1, 'penalty': 'l2'}
48best score : 0.9474200393672962
49
  1. is there any easier way to get predictions on the train data back? we already have the logreg_cv object. I used below method to get the predictions back. Is there a better way to do the same?

logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]

############################

############update 1

  1. Please answer question 1 from above. In the comment for the question it says The best score in GridSearchCV is calculated by taking the average score from cross validation for the best estimators. That is, it is calculated from data that is held out during fitting. From what I can tell, you are calculating predicted values from the training data and calculating an F1 score on that. Since the model was trained on that data, that is why the F1 score is so much larger compared to the results in the grid search

is that the reason I get below results #tuned hpyerparameters :(best parameters) {'C': 10.0, 'penalty': 'l2'} #best score : 0.7390325593588823

but when i do manually i get f1_score(y_train, final_prediction) 0.9839388145315489

2)

I tried to tune using f1_micro as suggested in the answer below. No error message. I am still not clear why f1_micro is not failing when precision fails

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]
17final_prediction=np.where(logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]&gt;=0.5,1,0)
18#https://www.statology.org/f1-score-in-python/
19from sklearn.metrics import f1_score
20#calculate F1 score
21f1_score(y_train, final_prediction)
220.9839388145315489
23from sklearn.model_selection import GridSearchCV
24
25grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
26logreg=LogisticRegression(solver = 'liblinear')
27logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='precision')
28logreg_cv.fit(X_train_vectors_tfidf, y_train)
29
30print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
31print(&quot;best score :&quot;,logreg_cv.best_score_)
32
33
34
35/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
36  _warn_prf(average, modifier, msg_start, len(result))
37/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
38  _warn_prf(average, modifier, msg_start, len(result))
39/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
40  _warn_prf(average, modifier, msg_start, len(result))
41/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
42  _warn_prf(average, modifier, msg_start, len(result))
43/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
44  _warn_prf(average, modifier, msg_start, len(result))
45/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
46  _warn_prf(average, modifier, msg_start, len(result))
47tuned hpyerparameters :(best parameters)  {'C': 0.1, 'penalty': 'l2'}
48best score : 0.9474200393672962
49from sklearn.model_selection import GridSearchCV
50
51grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;], &quot;solver&quot;:['liblinear','newton-cg'], 'class_weight':[{ 0:0.95, 1:0.05 }, { 0:0.55, 1:0.45 }, { 0:0.45, 1:0.55 },{ 0:0.05, 1:0.95 }]}# l1 lasso l2 ridge
52#logreg=LogisticRegression(solver = 'liblinear')
53logreg=LogisticRegression()
54logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1_micro')
55logreg_cv.fit(X_train_vectors_tfidf, y_train)
56
57tuned hpyerparameters :(best parameters)  {'C': 10.0, 'class_weight': {0: 0.45, 1: 0.55}, 'penalty': 'l2', 'solver': 'newton-cg'}
58best score : 0.7894909688013136
59

ANSWER

Answered 2021-Dec-09 at 23:12

You end up with the error with precision because some of your penalization is too strong for this model, if you check the results, you get 0 for f1 score when C = 0.001 and C = 0.01

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]
17final_prediction=np.where(logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]&gt;=0.5,1,0)
18#https://www.statology.org/f1-score-in-python/
19from sklearn.metrics import f1_score
20#calculate F1 score
21f1_score(y_train, final_prediction)
220.9839388145315489
23from sklearn.model_selection import GridSearchCV
24
25grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
26logreg=LogisticRegression(solver = 'liblinear')
27logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='precision')
28logreg_cv.fit(X_train_vectors_tfidf, y_train)
29
30print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
31print(&quot;best score :&quot;,logreg_cv.best_score_)
32
33
34
35/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
36  _warn_prf(average, modifier, msg_start, len(result))
37/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
38  _warn_prf(average, modifier, msg_start, len(result))
39/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
40  _warn_prf(average, modifier, msg_start, len(result))
41/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
42  _warn_prf(average, modifier, msg_start, len(result))
43/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
44  _warn_prf(average, modifier, msg_start, len(result))
45/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
46  _warn_prf(average, modifier, msg_start, len(result))
47tuned hpyerparameters :(best parameters)  {'C': 0.1, 'penalty': 'l2'}
48best score : 0.9474200393672962
49from sklearn.model_selection import GridSearchCV
50
51grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;], &quot;solver&quot;:['liblinear','newton-cg'], 'class_weight':[{ 0:0.95, 1:0.05 }, { 0:0.55, 1:0.45 }, { 0:0.45, 1:0.55 },{ 0:0.05, 1:0.95 }]}# l1 lasso l2 ridge
52#logreg=LogisticRegression(solver = 'liblinear')
53logreg=LogisticRegression()
54logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1_micro')
55logreg_cv.fit(X_train_vectors_tfidf, y_train)
56
57tuned hpyerparameters :(best parameters)  {'C': 10.0, 'class_weight': {0: 0.45, 1: 0.55}, 'penalty': 'l2', 'solver': 'newton-cg'}
58best score : 0.7894909688013136
59res = pd.DataFrame(logreg_cv.cv_results_)
60res.iloc[:,res.columns.str.contains(&quot;split[0-9]_test_score|params&quot;,regex=True)]
61 
62                           params  split0_test_score  split1_test_score  split2_test_score
630   {'C': 0.001, 'penalty': 'l2'}           0.000000           0.000000           0.000000
641    {'C': 0.01, 'penalty': 'l2'}           0.000000           0.000000           0.000000
652     {'C': 0.1, 'penalty': 'l2'}           0.973568           0.952607           0.952174
663     {'C': 1.0, 'penalty': 'l2'}           0.863934           0.851064           0.836449
674    {'C': 10.0, 'penalty': 'l2'}           0.811634           0.769547           0.787838
685   {'C': 100.0, 'penalty': 'l2'}           0.789826           0.762162           0.773438
696  {'C': 1000.0, 'penalty': 'l2'}           0.781003           0.750000           0.763871
70

You can check this:

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]
17final_prediction=np.where(logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]&gt;=0.5,1,0)
18#https://www.statology.org/f1-score-in-python/
19from sklearn.metrics import f1_score
20#calculate F1 score
21f1_score(y_train, final_prediction)
220.9839388145315489
23from sklearn.model_selection import GridSearchCV
24
25grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
26logreg=LogisticRegression(solver = 'liblinear')
27logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='precision')
28logreg_cv.fit(X_train_vectors_tfidf, y_train)
29
30print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
31print(&quot;best score :&quot;,logreg_cv.best_score_)
32
33
34
35/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
36  _warn_prf(average, modifier, msg_start, len(result))
37/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
38  _warn_prf(average, modifier, msg_start, len(result))
39/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
40  _warn_prf(average, modifier, msg_start, len(result))
41/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
42  _warn_prf(average, modifier, msg_start, len(result))
43/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
44  _warn_prf(average, modifier, msg_start, len(result))
45/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
46  _warn_prf(average, modifier, msg_start, len(result))
47tuned hpyerparameters :(best parameters)  {'C': 0.1, 'penalty': 'l2'}
48best score : 0.9474200393672962
49from sklearn.model_selection import GridSearchCV
50
51grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;], &quot;solver&quot;:['liblinear','newton-cg'], 'class_weight':[{ 0:0.95, 1:0.05 }, { 0:0.55, 1:0.45 }, { 0:0.45, 1:0.55 },{ 0:0.05, 1:0.95 }]}# l1 lasso l2 ridge
52#logreg=LogisticRegression(solver = 'liblinear')
53logreg=LogisticRegression()
54logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1_micro')
55logreg_cv.fit(X_train_vectors_tfidf, y_train)
56
57tuned hpyerparameters :(best parameters)  {'C': 10.0, 'class_weight': {0: 0.45, 1: 0.55}, 'penalty': 'l2', 'solver': 'newton-cg'}
58best score : 0.7894909688013136
59res = pd.DataFrame(logreg_cv.cv_results_)
60res.iloc[:,res.columns.str.contains(&quot;split[0-9]_test_score|params&quot;,regex=True)]
61 
62                           params  split0_test_score  split1_test_score  split2_test_score
630   {'C': 0.001, 'penalty': 'l2'}           0.000000           0.000000           0.000000
641    {'C': 0.01, 'penalty': 'l2'}           0.000000           0.000000           0.000000
652     {'C': 0.1, 'penalty': 'l2'}           0.973568           0.952607           0.952174
663     {'C': 1.0, 'penalty': 'l2'}           0.863934           0.851064           0.836449
674    {'C': 10.0, 'penalty': 'l2'}           0.811634           0.769547           0.787838
685   {'C': 100.0, 'penalty': 'l2'}           0.789826           0.762162           0.773438
696  {'C': 1000.0, 'penalty': 'l2'}           0.781003           0.750000           0.763871
70lr = LogisticRegression(C=0.01).fit(X_train_vectors_tfidf,y_train)
71np.unique(lr.predict(X_train_vectors_tfidf))
72array([0])
73

And that the probabilities predicted drift towards the intercept:

1#lets try gridsearchcv
2#https://www.kaggle.com/enespolat/grid-search-with-logistic-regression
3
4from sklearn.model_selection import GridSearchCV
5
6grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
7logreg=LogisticRegression(solver = 'liblinear')
8logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1')
9logreg_cv.fit(X_train_vectors_tfidf, y_train)
10
11print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
12print(&quot;best score :&quot;,logreg_cv.best_score_)
13
14#tuned hpyerparameters :(best parameters)  {'C': 10.0, 'penalty': 'l2'}
15#best score : 0.7390325593588823
16logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]
17final_prediction=np.where(logreg_cv.predict_proba(X_train_vectors_tfidf)[:,1]&gt;=0.5,1,0)
18#https://www.statology.org/f1-score-in-python/
19from sklearn.metrics import f1_score
20#calculate F1 score
21f1_score(y_train, final_prediction)
220.9839388145315489
23from sklearn.model_selection import GridSearchCV
24
25grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;]}# l1 lasso l2 ridge
26logreg=LogisticRegression(solver = 'liblinear')
27logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='precision')
28logreg_cv.fit(X_train_vectors_tfidf, y_train)
29
30print(&quot;tuned hpyerparameters :(best parameters) &quot;,logreg_cv.best_params_)
31print(&quot;best score :&quot;,logreg_cv.best_score_)
32
33
34
35/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
36  _warn_prf(average, modifier, msg_start, len(result))
37/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
38  _warn_prf(average, modifier, msg_start, len(result))
39/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
40  _warn_prf(average, modifier, msg_start, len(result))
41/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
42  _warn_prf(average, modifier, msg_start, len(result))
43/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
44  _warn_prf(average, modifier, msg_start, len(result))
45/usr/local/lib/python3.7/dist-packages/sklearn/metrics/_classification.py:1308: UndefinedMetricWarning: Precision is ill-defined and being set to 0.0 due to no predicted samples. Use `zero_division` parameter to control this behavior.
46  _warn_prf(average, modifier, msg_start, len(result))
47tuned hpyerparameters :(best parameters)  {'C': 0.1, 'penalty': 'l2'}
48best score : 0.9474200393672962
49from sklearn.model_selection import GridSearchCV
50
51grid={&quot;C&quot;:np.logspace(-3,3,7), &quot;penalty&quot;:[&quot;l2&quot;], &quot;solver&quot;:['liblinear','newton-cg'], 'class_weight':[{ 0:0.95, 1:0.05 }, { 0:0.55, 1:0.45 }, { 0:0.45, 1:0.55 },{ 0:0.05, 1:0.95 }]}# l1 lasso l2 ridge
52#logreg=LogisticRegression(solver = 'liblinear')
53logreg=LogisticRegression()
54logreg_cv=GridSearchCV(logreg,grid,cv=3,scoring='f1_micro')
55logreg_cv.fit(X_train_vectors_tfidf, y_train)
56
57tuned hpyerparameters :(best parameters)  {'C': 10.0, 'class_weight': {0: 0.45, 1: 0.55}, 'penalty': 'l2', 'solver': 'newton-cg'}
58best score : 0.7894909688013136
59res = pd.DataFrame(logreg_cv.cv_results_)
60res.iloc[:,res.columns.str.contains(&quot;split[0-9]_test_score|params&quot;,regex=True)]
61 
62                           params  split0_test_score  split1_test_score  split2_test_score
630   {'C': 0.001, 'penalty': 'l2'}           0.000000           0.000000           0.000000
641    {'C': 0.01, 'penalty': 'l2'}           0.000000           0.000000           0.000000
652     {'C': 0.1, 'penalty': 'l2'}           0.973568           0.952607           0.952174
663     {'C': 1.0, 'penalty': 'l2'}           0.863934           0.851064           0.836449
674    {'C': 10.0, 'penalty': 'l2'}           0.811634           0.769547           0.787838
685   {'C': 100.0, 'penalty': 'l2'}           0.789826           0.762162           0.773438
696  {'C': 1000.0, 'penalty': 'l2'}           0.781003           0.750000           0.763871
70lr = LogisticRegression(C=0.01).fit(X_train_vectors_tfidf,y_train)
71np.unique(lr.predict(X_train_vectors_tfidf))
72array([0])
73# expected probability
74np.exp(lr.intercept_)/(1+np.exp(lr.intercept_))
75array([0.41764462])
76
77lr.predict_proba(X_train_vectors_tfidf)
78 
79array([[0.58732636, 0.41267364],
80       [0.57074279, 0.42925721],
81       [0.57219143, 0.42780857],
82       ...,
83       [0.57215605, 0.42784395],
84       [0.56988186, 0.43011814],
85       [0.58966184, 0.41033816]])
86

For the question on "get predictions on the train data back", i think that's the only way. The model is refitted on the whole training set using the best parameters, but the predictions or predicted probabilities are not stored. If you are looking for the values obtained during train / test, you can check cross_val_predict

Source https://stackoverflow.com/questions/70264157

Community Discussions contain sources that include Stack Exchange Network

Tutorials and Learning Resources in Grid

Tutorials and Learning Resources are not available at this moment for Grid

Share this Page

share link

Get latest updates on Grid