Interactive Data Table in Pluto.jl (via a silly hack)

It work! thanks man, you save my report Thanks @mthelm85 and @andre1sk

1 Like

I just tried referencing a local .js file within a notebook and I got an error in my browser telling me I canā€™t include local resources.

Yeah I sadly also discovered thisā€¦ Do you know why this is the case? Looking at the ā€˜sourcesā€™ tab in Chrome dev tools, it looks like the Pluto server only serves the static content needed for the interface, not custom JS files in the notebook repo.

@fonsp do you know if it is possible to serve custom JS files (for custom visualizations and such)?

@mthelm85 Hi, I have another question, It is possible to use the function

data_table(JSON2.write(data))

more than once in Pluto, the first time I use this print the table perfectly, but the second time I call the function whit other data, it doesnā€™t show anything.

Thanks in advance.

:laughing:, I never thought about that. Itā€™s because of the static id for the <div> where the Vue app is rendered. Use this version instead:

using JSON2
using Random
using Tables

function data_table(table)
	app_id = randstring('a':'z')
	d = Dict(
        "headers" => [Dict("text" => string(name), "value" => string(name)) for name in Tables.columnnames(table)],
        "data" => [Dict(string(name) => row[name] for name in Tables.columnnames(table)) for row in Tables.rows(table)]
    )
	djson = JSON2.write(d)
	
	return HTML("""
		<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
		<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">

	  <div id="$app_id">
		<v-app>
		  <v-data-table
		  :headers="headers"
		  :items="data"
		></v-data-table>
		</v-app>
	  </div>

	  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
	  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
	
	<script>
		new Vue({
		  el: '#$app_id',
		  vuetify: new Vuetify(),
		  data () {
				return $djson
			}
		})
	</script>
	<style>
		.v-application--wrap {
			min-height: 10vh;
		}
		.v-data-footer__select {
			display: none;
		}
	</style>
	""")
end

Note that you now need to load the Random package.

2 Likes

@mthelm85 Thanks man actually I change the function just a little, but your advice help me a lot. Now Iā€™m trying to save this function in a Module so I can call it from any .jl or Pluto notebook essentially, just typing using MyModule o the name you want, if you have the time it would be really usefull to have this module ready to download from GitHub, this hack that you did help me a lot, I know that is gonna be useful for a lot of people out there.

function data_table(data,columns,ids)
return HTML("""
	<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
	<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">

  <div id="$ids">
	<v-app>
	  <v-data-table
		:headers="headers"
		:items="Valores"
		:items-per-page=$columns
	></v-data-table>
	</v-app>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>

<script>
	new Vue({
	  el: '#$ids',
	  vuetify: new Vuetify(),
	  data () {
			return $data 
		}
	})
</script>
<style>
	.v-application--wrap {
		min-height: 1vh;
	}
	.v-data-footer__select {
		display: none;
	}
</style>
""")
end

Thanks again

Iā€™m trying to save this function in a Module so I can call it from any .jl or Pluto notebook essentially, just typing using MyModule o the name you want, if you have the time it would be really usefull to have this module ready to download from GitHub, this hack that you did help me a lot, I know that is gonna be useful for a lot of people out there.

As requested:

I wouldnā€™t want to pollute the Julia registry with something like this but you can Pkg.add("https://github.com/mthelm85/PlutoDataTable.jl") and it should work fine when calling using PlutoDataTable in your notebook (assuming you do the install in the same environment youā€™re running the notebook from).

I would encourage you to check out PkgTemplates.jl as it makes doing things like this very easy :wink: I was able to throw this together, push to GitHub and then test in about 10 minutes.

4 Likes

Just tried it out, amazing!

It would be great to have this functionality in the PlutoUI package.

1 Like

Maybe @fonsp can chime in. Iā€™m happy to submit a PR to Pluto or PlutoUI but I suspect this approach is too hacky to incorporate into either of those :smile: Theyā€™re developing Pluto with React.js so I think the end game here would be to have interactive tables as the default via some React solution (which will undoubtedly be a lot of work). This workaround makes use of Vue.js and Vuetify and brings in those assets via a CDN.

Normally I would say that while we wrote Pluto with react, you can use anything inside user code! But in that case you are right, we want the interactive viewer to be built in for tables specifically

2 Likes

Hiā€™
Iā€™m really newā€¦ to all. But Iā€™m learning by doing. Until now I have been using python and Jupyter to process some csv, but I want to move to Julia and Pluto. So, my question.
Do you think that I can use this procedure but instead of do a csv to dict, make a csv to dataframe and the to json?

The way the function is currently written you wonā€™t need to do any of that. You can load your csv directly:

using CSV
using PlutoDataTable

data = CSV.File("path/to/your/file.csv")
data_table(data) # this should output the interactive table

Note that TableView.jl works well with Jupyter notebooks. So you donā€™t need PlutoDataTable to view data easily in a notebook.

1 Like

great, because first I make some operations on two csv merging data, the i an create de final csv and send it directly to the function

sorry, why intermediate csv files? you can just work with everything in memory using DataFrames

Right now, I use DataFrame to marge an operate wit de data in those CSV. but finally I have to send to my colleagues (that don use python or Julia or notebooks) an csv processed. So, what I want is make engaging notebook with Pluto if I can, to make then come to the dark side. And finally stop using googl services.

Dear All,

I am new to julialang.org and I am enclosing a little module of mine based on ideas from this interesting discussion.

Special thanks to mthelm85 for his great job!

module TabularUtilities

using DataFrames
using DelimitedFiles
using CSV
using JSON2

export show_data_table

"""
This module generalizes a solution given to a particular CSV case.
Its purpose is to attain a more general solution to be applied to bidimensional tables
and is based on ideas from the `Julia Discourse Forum`:

> Interactive Data Table in Pluto

https://discourse.julialang.org/t/interactive-data-table-in-pluto-jl-via-a-silly-hack/44678

> A nice little hack for creating interactive data tables in my Pluto notebooks (mthelm85)

Obs.: There are some coding simplifications and optimizations left to future revision.

"""

"Delimited_file -> Dictionary"
function delimited2dict(delimited_path, dlmiter='|')
	delimited_file = readdlm(delimited_path, dlmiter, header=true, skipblanks=true)
	return Dict(
	"headers" => [Dict("text" => str, "value" => str) for str in delimited_file[2]],
  "states" => [Dict([(delimited_file[2][i], delimited_file[1][j, i]) for i in 1:length(delimited_file[2])]) for j in 1:size(delimited_file[1],1)])
end

"DataFrame -> Dictionary"
function delimited2dict(df::DataFrame)
	delimited_file = (convert(Matrix, df), reshape(names(df),(1,length(names(df)))))
	return Dict(
	"headers" => [Dict("text" => str, "value" => str) for str in delimited_file[2]],
  "states" => [Dict([(delimited_file[2][i], delimited_file[1][j, i]) for i in 1:length(delimited_file[2])]) for j in 1:size(delimited_file[1],1)])
end

"Matrix -> Dictionary"
function delimited2dict(mt::AbstractMatrix)
	delimited_file = (mt, reshape([string(i) for i in 1:length(mt[1,:])],(1,length(mt[1,:]))))
	return Dict(
	"headers" => [Dict("text" => string("C", str), "value" => str) for str in delimited_file[2]],
	"states" => [Dict([(delimited_file[2][i], delimited_file[1][j, i]) for i in 1:length(delimited_file[2])]) for j in 1:size(delimited_file[1],1)]
	          )
end

"table_dict  ->  html table"
function data_table_html(data, num_items)
	return HTML("""
		<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
		<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">

	  <div id="app">
		<v-app>
		  <v-data-table
		  :headers="headers"
		  :items="states"
		  :items-per-page=$num_items
		></v-data-table>
		</v-app>
	  </div>

	  <script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
	  <script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>

	<script>
		new Vue({
		  el: '#app',
		  vuetify: new Vuetify(),
		  data () {
				return $data
			}
		})
	</script>
	<style>
		.v-application--wrap {
			min-height: 10vh;
		}
		.v-data-footer__select {
			display: none;
		}
	</style>
	""")
end

"Include a html data table into a Pluto notebook: delmtd_path to a CSV file"
function show_data_table(delmtd_path::String, dlmiter='|', n_lines=10)
  data_table_html(JSON2.write(delimited2dict(delmtd_path, dlmiter)), n_lines)
end

show_data_table(delmtd_path::String, n_lines) = show_data_table(delmtd_path, '|', n_lines)

function show_data_table(df::DataFrame, n_lines=10)
  data_table_html(JSON2.write(delimited2dict(df)), n_lines)
end

function show_data_table(mt::AbstractMatrix, n_lines=10)
  data_table_html(JSON2.write(delimited2dict(mt)), n_lines)
end

end # module

Works great

1 Like