Nicd
So I have this page and I’m thinking of making a new version of it with LiveView:
As you can see, there are many different graphs on the page. Currently on the frontend I have a JS system where each graph is its own component and defines what kind of data it is interested in. Since many graphs can be interested in the same data (such as the two language lists), I don’t want to duplicate queries.
Additionally, the page is live updated, so all the graphs get the new data and decide what to do with it.
Now I’m wondering how to structure this with LiveView. I just started hammering it directly and got the top bar and top languages list working, but obviously the LV module will become a mess with all the data retrievals. So I want to structure it in a cleaner way. I have some ideas but would be nice to get yours.
A final aim here is that some time in the future, the specific graphs and their positions could be configurable, so if possible, I don’t want to hardcode them in the template. But I don’t know what LV’s change tracking would think of that.
My plan is to do something like this for now (pseudo-ish):
defmodule ProfileLive do
@graphs [
TopBarGraph,
Last2WeeksGraph,
TopLanguagesGraph,
...
]
And then somehow render the stuff based on that list (then later it could be made configurable). The modules would have something like
defmodule TopBarGraph do
def wants_data(), do: MapSet.new([:user, :total_xp, :recent_xp, :date_xps])
...
end
and then the LV module would combine the needed datas and retrieve those, providing them to the graphs. Does that sound reasonable?
But if the data needs are dynamic like that, I would need to render the component in a generic way like
<%= live_component(@socket, TopBarGraph, data: @TopBarGraph_data) %>
right? I can think of a couple other ways too, but I’m not sure which of them would work with LV’s change tracking so it doesn’t have to render everything on every update.
I think I can get the live updates working once I figure out a good structure for the basic setup. So, any ideas welcome.
Have you done anything similar?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex











First 10 of 14 Posts
mindok
I’ve just been doing something similar, building pluggable charts for borehole data.
What I ended up doing was:
mountorhandle_params, I iterate the data extraction configuration to build a map of extracted datasets using the different extractor modules and put them on the socket assocket.assigns.dataset(in my case about 20Mb per liveview process - thankfully there’s only ever a handful of users!). There are also some derived datasets generated from the base ones (e.g. moving averages) - the data extraction process runs the base ones first, then generates the derived ones.In the case where you are listening for live updates from other parts of the system I would just update the data held in
socket.assigns.datasetsin the appropriatehandle_infoThat sorts out the data extraction.
I also have pluggable renderers for each different type of visualisation I want to show (mostly based on Contex FWIW - they emit SVG so no JS hooks are required).
I then have a definition for each display element that defines the named dataset to use, the pluggable rendering module and any settings to control the rendering. This is added to the socket as
display_blocksFinally I have a component that is embedded in the main liveview along the lines of what you have:
MyLayoutComponenthandles organising height & width of all the sub-components based on the settings in the passeddisplay_blocks, passing in the correct dataset and settings and invoking the rendering. The whole thing re-renders when anything changes at the moment (data, settings or other things like the currently selected item - akaother_stuff), which is a bit inefficient but actually performs ok. I feel the code and approach is reasonably well organised and easy enough to extend with additional visualisations.I hope this makes sense!
Nicd
Thanks! This hints that my original idea would at least work. Shame that you lose the LV change tracking accuracy, though, so willing to hear any other ideas that would allow me to keep it.
mindok
There’s nothing structural there that makes me lose change-tracking. Just laziness on my part in not wrapping the rendering functions into LiveComponents. As it happens, I now have a glitch in the rendering sequence and some JS interop that is forcing me to do it properly for a couple of them!
paulstatezny
Have you looked into Surface?
Nicd
I have not. Is there anything specific there that would apply to this situation?
I have started building a first iteration of this and will post here how it works when there’s an MVP.
paulstatezny
Disclaimer: I’ve contributed to Surface and built the SurfaceFormatter, so I’m biased.
Surface provides a somewhat “React-like” syntax for building reusable components. You can define the interface of what data should be passed into the component, with some compile time checks. It’s a more mature development experience for doing components than vanilla LiveView components in my opinion.
Nicd
I hacked on it last week and came up with a first iteration, the meat of it is here: lib/code_stats_web/profile_live · f6ba35970a16f8fff89c0cbc38d80ecce9f53b16 · CodeStats / code-stats · GitLab
There are no docblocks yet and there’s a couple of unused functions laying around, but it’s a start. I’ll describe here how it works so that I can copy these to the docblocks later.
The building blocks here are Graphs, DataProviders, SharedData, and the live view itself. The relationships are something like:
So the live view has many graphs, these are managed by Graphs.Catalogue. The graphs’ responsibility is to receive data and render it, and they are normal live components (not yet sure if they will need to be stateful or stateless). They also have a function
render_wrapperthat is used to render them into the live view, so that I don’t need to do justdata: @dataassigns, but I can instead get accurate assigns for each graph. Example fromUserInfo:I don’t actually know yet how this affects change tracking, because the live updates aren’t implemented, but I’m hoping for the best. And I think it’s nicer that the component only gets the data it needs and not all of it.
Now the graphs in turn have a set of DataProviders that they get data from. The purpose of the providers is to retrieve the initial dataset, and to update it (in the future) when any events come in. Each graph can get data from many providers and the providers can be used for many graphs.
Now, since some providers rely on the same data (like the last 12 hours of events) and I don’t want to repeat the queries, there is finally the SharedData module. Providers specify what shared data they need and SharedData is responsible for requesting it.
Finally we get to combining all of this in the live view:
Then in the template, I use
which in turn is just a
forstatement that calls therender_wrapperof all the graphs.Now, this all works great currently, but I don’t have the live updates implemented yet, so it’s not a full featured prototype. Additionally, I have a couple of open questions still:
renderbut instead just call the functions directly. I wonder if this affects LV’s change tracking ability? I don’t see any other reason to userenderbecause it would require a view and extra hassle.~Lwrapping is necessary or if I could just run the code directly.I know that’s a lot to read and I don’t expect anyone to invest too much time into this, but if you have any comments or ideas or insults, I’d be glad to hear them.
Nicd
Looks like I lose change tracking with this method. I guess it’s because I just call a function with
<%= Graphs.Catalogue.render_graphs(assigns) %>and then the function looks like this:Is that it? How could I keep change tracking with this structure?
EDIT: José mentioned this should be fine, so I guess there’s just something I don’t understand about LV. I will study more myself before making any more posts.
the_wildgoose
I have found Surface to be a very nice solution for liveview, it allows you to build “components” which are easily composable and the end result feels very much like writing HTML, only with magic tags which explode into whole liveview components. Very nice.
I think this git repo might be a great example of what you are trying to develop. I found it useful both for it’s content and for ideas on using LiveView. Shout out to the author!
https://github.com/code-shoily/covid19
Nicd
Hey, thanks for the recommendation.
I quickly looked at the repository and it seems the components are still hardcoded in the live layout (for example DashboardLive). I.e. the component tags are written out in the template so their order is not easy to control. I wanted to be able to not specify the graph components in the template if possible, so that I can configure them per-user. I know I can use CSS Grid for positioning out of order on the screen, but I’d like to try if I can do it generically in LV.
I will come back to this thread when I have something working.