CartoDB / cartoframes

CARTO Python package for data scientists
BSD 3-Clause "New" or "Revised" License
251 stars 63 forks source link

Allow multiple visualizations #877

Closed simon-contreras-deel closed 5 years ago

simon-contreras-deel commented 5 years ago

We were using matplotlib to display different images as we can see in the documentation:

Screenshot 2019-07-29 at 18 11 09 Screenshot 2019-07-29 at 18 11 04

We want to bring this functionality back. The main purpose is to have the possibility of combining several visualizations but we also want to keep using matplotlib because it's also being used to create other type of visualizations. After a user testing process, this is one of the features we want to support.

andy-esch commented 5 years ago

We need to find a way to turn the interactive/cartovl maps into a static map somehow

Related #785

VictorVelarde commented 5 years ago

I would focus in the current use case: being able to create several static images from a Map (just the basemap with the layers, so no legends nor widgets), inside a matplotlib layout.

For more complex stuff, like multiple interactive maps in sync, I would propose a new issue and further analysis with the users (and maybe explore just some variants, as 'double-map in sync'

elenatorro commented 5 years ago

After doing some research, I would say that what we want to achieve in this task is not possible, or at least I haven't been able to find a good approach.

We're facing the following problems:

First of all, we want to convert the visualization to png through the canvas in the client. This is possible and it is very easy to generate static maps. The way to do so is from the JavaScript code in the iframe, by getting the base64 code from the canvas and setting it as the src of an <img/>. But this is because the map has been rendered in the cell.

Why was it working with carto.js? Cause in this case, we were geeting the png images from the server.

Another approach would be to enable the user to generate the graphs in our html layout. This would be possible using, for example, mpld3.

The mpld3 project brings together Matplotlib, the popular Python-based graphing library, and D3js, the popular JavaScript library for creating interactive data visualizations for the web. The result is a simple API for exporting your matplotlib graphics to HTML code which can be used within the browser, within standard web pages, blogs, or tools such as the IPython notebook.

The cost of this would be:

My proposal:

We could offer the possibility of generating static maps as it's shown in the screenshot above (image + title) but not through matplotlib. I think this first solution would be ok for a first version, and we can keep working on including matplotlib charts in the future.

Q&A:

If anyone has experience doing this before and knows a better solution, I'd be more than happy to hear it :)

cmongut commented 5 years ago

Nice research!

This means that we can not integrate with matplotlib and we can't export to an image (through code) either, isn't it?

What is your proposal for creating the grid? We will try to get more feedback but I think we shouldn't spend time making the visualizations static.

elenatorro commented 5 years ago

I just opened a PR https://github.com/CartoDB/cartoframes/pull/886 to set static images. My idea is to create our own Grid class that displays a html template with different maps. There two options
I've been thinking of:

  1. Create an iframe per visualization (I would go for this one 👍)
  2. Create several maps in the same iframe

The second one is more complex because we'd have to deal in the same javascript code with several maps, and this would also mean to have a worse performance. Although one might think the first option could be worse because it has to load each library for every visualization (i.e: load CARTO VL 6 times if there are 6 visualizations), these libraries are cached by the browser. We'd have to test the impact in the notebook of having several visualizations.

I would keep the maps in the grid static by default, and as @VictorVelarde mentions in his comment, deal with interactivity features in a different feature. But this is my personal opinion, I'm totally open to other options :)

VictorVelarde commented 5 years ago

I'm a bit worried about the performance / stability when using multiple interactive maps. Maybe we could make a test using 4, 9, 12 maps... to see if we find a limitation.

Regarding the 'static' map I have seen your PR @elenatorro , and it looks nice. Could we somehow get the image from the cell back into python, loaded as an image?

elenatorro commented 5 years ago
  1. About performance:

You're right, and this is something I'm testing in the PoC for this issue. Remember also that it's possible to create mutliple static visualizations (images), and in this case we get rid of the map, which means we'd only have performance issues in interactive visualizations.

  1. About sending the image back to python

I've tested how to send the image as a base64 url back to Python from JavaScript, and that's possible. But using this image, once the cell has been already rendered, is not straight forward. I'm thinking about creating a document with all the research I've done so far and share it in order to see if someone else has experience with this and can help us.

  1. About future sync visualizations:

I know we're not addressing this feature in this issue. However, it's something we've discussed we might want in the short-mid term. If we go for having an iframe per visualization, we'd need to know how to communicate between iframes, I just wanted to point that out.

simon-contreras-deel commented 5 years ago

AFAIK, the only way to create images from HTML is using rendering engine (as webkit, x11 or similar). So, without the browser, we would depend on local engines as x11 installed in the user computer...

So I don't see another real solution that the one you have described

makella commented 5 years ago

@elenatorro

As you and @VictorVelarde are discussing above, I agree a first good step would be to try out making a series of static map image grids with up to 9 or max 12 maps.

From what I can tell, the actual size (height / width) of each map's grid cell is related to the zoom and extent of the map inside. I'm not sure if it is possible to dynamically adjust the grid size based on that information and/or if we can provide users with the option to modify the height/width in CF. I would like to do some further exploration on this before we talk tomorrow.

Regarding performance, as we were talking about, we can also do some testing for interactive/animated maps further down the line and if necessary, set some "rules" for our users in the documentation..."we do not recommend showing more than 4 animated maps in a grid" or something similar based on amount of data, viewport, etc.

Regardless, I think the static image map grid will be a big hit and a great place to start!

elenatorro commented 5 years ago

I'm preparing the grid with CSS to do some testing (it would look similar to this codepen example. Regarding the best way of setting the layout distribution in terms of API definition, I've come up with two options:

  1. Use multidimensional NxM array of Maps to the MapGrid, which will match the final layout representation
MapGrid([
    [Map(), Map(), Map()],
    [Map(), Map(), Map()],
    [Map(), Map(), Map()]
])
  1. Use a single array of Maps and then set the NxM size
MapGrid([
    Map(), Map(), Map(),
    Map(), Map(), Map(),
    Map(), Map(), Map()
], 3, 3)

Which one do you think would fit better?

cc @makella @oleurud @cmongut @VictorVelarde

VictorVelarde commented 5 years ago

I prefer the second one over the jagged array.

I also think that defining one dimension, the width, should be enough, letting the other be implicit.

So I see something like MapGrid(listOfMaps, 3) with listOfMaps probably built in a for loop outside

makella commented 5 years ago

I prefer the second option as well. Nice job!

andy-esch commented 5 years ago

Hey all, I hope this is the right place to mention this and I don't add noise 🙊

Once we get PNGs we can put them into matplotlib axes with matplotlib.pyplot.imread. We could also rely on matplotlib.pyplot.subplots to add them to the grid too instead of creating our own custom grid. The big advantage I see is that people can integrate maps with charts more easily this way.

elenatorro commented 5 years ago

Hi, Andy! The problem is that we can't get the image directly with CARTO VL until it's being rendered. We can talk about this today or tomorrow in a meeting, I'll send you an invite, thanks!!!

elenatorro commented 5 years ago

Another note: It is possible to use the image url in base64 and include it with matplotlib, it's not as straight forward cause you need to render the map (using is_static=True) in a different cell first, and the save the image url or save the image as a file and use it later:

Screenshot 2019-08-08 at 15 46 20