-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Charts support #311
Charts support #311
Conversation
Codecov Report
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. @@ Coverage Diff @@
## master #311 +/- ##
==========================================
+ Coverage 93.31% 93.70% +0.38%
==========================================
Files 5 5
Lines 359 381 +22
==========================================
+ Hits 335 357 +22
Misses 24 24
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very interesting! I left some comments. I think it's mostly done - the main thing at the moment is getting the chart data to be returned dynamically.
admin_ui/package.json
Outdated
"chart.js": "^4.3.0", | ||
"core-js": "^3.6.5", | ||
"flatpickr": "^4.6.9", | ||
"highcharts": "^11.0.1", | ||
"is-svg": "^4.3.1", | ||
"js-cookie": "^2.2.1", | ||
"json-bigint": "^1.0.0", | ||
"ssri": "^8.0.1", | ||
"vue": "^2.7.14", | ||
"vue-chartkick": "^0.6.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the interaction between chart.js
, highcharts
and vue-chartkick
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is chart.js
redundant? It looks like we're just using highcharts
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tried chart.js
? It's slightly smaller than highcharts, and it supports tree shaking, so we might get a smaller bundle size:
https://bundlephobia.com/package/[email protected]
https://bundlephobia.com/package/[email protected]
Also, the licensing on highcharts looks complicated, so I'd rather stay away from it. chart.js is MIT like Piccolo Admin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My mistake. I tried both libraries, so it accidentally stayed in package.json
. This PR uses highcharts
, but I will switch to Chart.js
in the next commit.
@@ -1,5 +1,5 @@ | |||
<template> | |||
<div> | |||
<div class="sidebar_wrapper"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for a menu sidebar of equal width between the Home
page and other pages. Now this is not the case because the sidebar menu on the Home
page is wider than on other pages. I can remove it if you want?
admin_ui/src/router.ts
Outdated
@@ -8,6 +8,7 @@ import Login from './views/Login.vue' | |||
import ChangePassword from './views/ChangePassword.vue' | |||
import RowListing from './views/RowListing.vue' | |||
import AddForm from './views/AddForm.vue' | |||
import GetCharts from './views/GetCharts.vue' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we just call this view Chart.vue
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will change that.
docs/source/charts/index.rst
Outdated
Charts | ||
====== | ||
|
||
Piccolo Admin can display different types of charts based on yours |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo here: yours
-> your
.
docs/source/charts/index.rst
Outdated
|
||
.. literalinclude:: ./examples/app.py | ||
|
||
Piccolo Admin will then show a charts in the UI. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
charts
-> chart
piccolo_admin/endpoints.py
Outdated
|
||
""" | ||
|
||
def __init__(self, title: str, chart_type: str, data: t.List[t.Any]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is defined as a dataclass - so we can drop the @dataclass
decorator and just keep the __init__
method. With our docs, Sphinx handles normal Python classes better than dataclasses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the type annotation for data is wrong. In the docstring it shows that we can pass in an async function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, we can pass the result of async or sync function which is a list of lists
. I will change the annotation to t.List[t.List[t.Any]]
. I will also update the docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if you just pass the result, the chart is no longer dynamic. I think each time the chart is viewed the data should be calculated from the function.
piccolo_admin/endpoints.py
Outdated
title: str | ||
chart_slug: str | ||
chart_type: str | ||
data: t.List[t.Any] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the data returned a list of tuples ... we could define it more precisely like:
data: t.List[t.Tuple[str, t.Union[int, float, decimal.Decimal]]]
Presumably each element is a string, followed by some kind of number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returned data must be list of lists
. I will change the annotation to t.List[t.List[t.Any]]
.
piccolo_admin/endpoints.py
Outdated
|
||
def get_charts(self) -> t.List[ChartConfigResponseModel]: | ||
""" | ||
Returns all charts registered with the admin. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have an extra space here -> Returns all
piccolo_admin/endpoints.py
Outdated
|
||
def get_single_chart(self, chart_slug: str) -> ChartConfigResponseModel: | ||
""" | ||
Returns single chart. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-> Returns a single chart
piccolo_admin/example.py
Outdated
charts=[ | ||
ChartConfig( | ||
title="Movie count", | ||
chart_type="Pie", | ||
data=[ | ||
["George Lucas", 4], | ||
["Peter Jackson", 6], | ||
["Ron Howard", 1], | ||
], | ||
), | ||
ChartConfig( | ||
title="Director gender", | ||
chart_type="Column", | ||
data=[["Male", 7], ["Female", 3]], | ||
), | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add examples for each chart we support - I think we support Pie, Line, Column, Bar and Area.
Also, I think the main missing piece is the data is hardcoded at the moment, but in the docs we show how the user can pass in an async function, so the data can be fetched dynamically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, you're right, the data is hardcoded because we can't run database queries in the piccolo admin example (piccolo_conf
error), but we can use a coroutine or sync function in a normal application. I will change the test to run the hardcoded data from the coroutine in example.py
so that the user can see an example of how to use it. Something like this
async def fetch_chart_data() -> t.List[t.List[t.Any]]:
return [
["George Lucas", 4],
["Peter Jackson", 6],
["Ron Howard", 1],
]
chart_data = asyncio.run(fetch_chart_data())
APP = create_admin(charts=[
ChartConfig(
title="Movie count Pie",
chart_type="Pie",
data=chart_data,
),
ChartConfig(
title="Movie count Line",
chart_type="Line",
data=chart_data,
),
ChartConfig(
title="Movie count Column",
chart_type="Column",
data=chart_data,
),
ChartConfig(
title="Movie count Bar",
chart_type="Bar",
data=chart_data,
),
ChartConfig(
title="Movie count Area",
chart_type="Area",
data=chart_data,
),
],
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem is the query is being run before the engine has been assigned to the tables.
If we make it dynamic then we won't have this problem.
async def fetch_chart_data() -> t.List[t.List[t.Any]]:
return await Movie.select() ...
APP = create_admin(charts=[
ChartConfig(
title="Movie count Pie",
chart_type="Pie",
source=fetch_chart_data,
),
]
Then every time the chart is fetched from the API, we run the function to get the latest data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can make the change if you like - it's up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course. That would be great. In the last commit I made your suggested changes. I think I covered everything except one thing I had trouble wrapping the chart pages in the BaseView
(to have a sidebar) due to a weird Vue router warning
vue-router.esm.js:16 [vue-router] missing param for named route "chart": Expected "chartSlug" to be defined
even though the chartSlug
is defined in the router like this
{
path: '/charts/:chartSlug',
name: 'chart',
component: Chart,
props: true
},
so I left the DetailBaseView
. It would be nice if you could look at that too. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll create a branch, and will play around.
Used in #316 |
Related to #146. @dantownsend I created this PR so you can try it, change it and play with this interesting new feature.