Skip to content
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

Chart experiments #316

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20,259 changes: 56 additions & 20,203 deletions admin_ui/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions admin_ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
"@fortawesome/vue-fontawesome": "^0.1.10",
"@types/js-cookie": "^2.2.6",
"axios": "^0.21.2",
"chart.js": "^2.9.4",
"core-js": "^3.6.5",
"flatpickr": "^4.6.9",
"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",
"vue-class-component": "^7.2.6",
"vue-flatpickr-component": "^8.1.7",
"vue-i18n": "^8.27.2",
Expand Down
31 changes: 31 additions & 0 deletions admin_ui/src/components/ChartsNav.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<ul>
<li v-bind:key="chartConfig.title" v-for="chartConfig in chartConfigs">
<router-link
:to="{
name: 'chart',
params: { chartSlug: chartConfig.chart_slug }
}"
class="subtle"
>
<font-awesome-icon icon="level-up-alt" class="rotated90" />
<span>{{ chartConfig.title }}</span>
</router-link>
</li>
</ul>
</template>

<script lang="ts">
import Vue from "vue"

export default Vue.extend({
computed: {
chartConfigs() {
return this.$store.state.chartConfigs
}
},
async mounted() {
await this.$store.dispatch("fetchChartConfigs")
}
})
</script>
59 changes: 59 additions & 0 deletions admin_ui/src/components/ChartsPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<template>
<div>
<h3>{{ chartConfigs.title }}</h3>
<pie-chart
width="650px"
height="450px"
:download="{ background: '#ffffff' }"
v-if="chartConfigs.chart_type == 'Pie'"
:data="chartConfigs.data"
></pie-chart>
<line-chart
width="650px"
height="450px"
:download="{ background: '#ffffff' }"
v-else-if="chartConfigs.chart_type == 'Line'"
:data="chartConfigs.data"
></line-chart>
<column-chart
width="650px"
height="450px"
:download="{ background: '#ffffff' }"
v-else-if="chartConfigs.chart_type == 'Column'"
:data="chartConfigs.data"
></column-chart>
<bar-chart
width="650px"
height="450px"
:download="{ background: '#ffffff' }"
v-else-if="chartConfigs.chart_type == 'Bar'"
:data="chartConfigs.data"
></bar-chart>
<area-chart
width="650px"
height="450px"
:download="{ background: '#ffffff' }"
v-else-if="chartConfigs.chart_type == 'Area'"
:data="chartConfigs.data"
></area-chart>
</div>
</template>

<script lang="ts">
import Vue from "vue"

export default Vue.extend({
computed: {
chartConfigs() {
return this.$store.state.chartConfigs
}
}
})
</script>

<style scoped lang="less">
h3 {
text-transform: capitalize;
text-align: center;
}
</style>
22 changes: 22 additions & 0 deletions admin_ui/src/components/SidebarNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@
</span>
</p>
<FormNav v-show="!isHiddenForms" />
<p
class="opaque"
v-if="chartConfigs.length > 0"
v-on:click="isHiddenCharts = !isHiddenCharts"
>
<font-awesome-icon icon="chart-bar" />{{ $t("Charts") }}
<span style="float: right">
<font-awesome-icon
icon="angle-down"
title="Show charts"
v-if="isHiddenCharts"
/>
<font-awesome-icon icon="angle-up" title="Hide charts" v-else />
</span>
</p>
<ChartsNav v-show="!isHiddenCharts" />
<p
class="opaque"
v-if="Object.keys(customLinks).length > 0"
Expand All @@ -56,25 +72,31 @@
import Vue from "vue"
import TableNav from "./TableNav.vue"
import FormNav from "./FormNav.vue"
import ChartsNav from "./ChartsNav.vue"
import LinksNav from "./LinksNav.vue"

export default Vue.extend({
data() {
return {
isHiddenTables: false,
isHiddenForms: false,
isHiddenCharts: false,
isHiddenLinks: false
}
},
components: {
TableNav,
FormNav,
ChartsNav,
LinksNav
},
computed: {
formConfigs() {
return this.$store.state.formConfigs
},
chartConfigs() {
return this.$store.state.chartConfigs
},
customLinks() {
return this.$store.state.customLinks
}
Expand Down
2 changes: 1 addition & 1 deletion admin_ui/src/components/TableNav.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div>
<div class="sidebar_wrapper">
<ul class="table_list">
<TableNavItem
v-bind:key="tableName"
Expand Down
2 changes: 2 additions & 0 deletions admin_ui/src/fontawesome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
faBars,
faCaretUp,
faCaretDown,
faChartBar,
faCheck,
faCircleNotch,
faCogs,
Expand Down Expand Up @@ -55,6 +56,7 @@ library.add(
faBars,
faCaretUp,
faCaretDown,
faChartBar,
faCheck,
faCircleNotch,
faCogs,
Expand Down
7 changes: 7 additions & 0 deletions admin_ui/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,10 @@ export interface FormConfig {
slug: string
description: string
}

export interface ChartConfig {
title: string
chart_slug: string
chart_type: string
data: any
}
5 changes: 4 additions & 1 deletion admin_ui/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import router from "./router"
import store from "./store"
import i18n from "./i18n"
import "./fontawesome"
import Chartkick from 'vue-chartkick'
import Chart from 'chart.js'

/*****************************************************************************/

Expand All @@ -30,7 +32,7 @@ axios.defaults.transformResponse = [
if (typeof data === "string") {
try {
data = JSONBig.parse(data)
} catch (e) {}
} catch (e) { }
}
return data
}
Expand All @@ -45,6 +47,7 @@ Vue.filter("readable", function (value) {
/*****************************************************************************/

Vue.config.productionTip = false
Vue.use(Chartkick.use(Chart))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// charts font color and gridlines in dark mode
if (localStorage.getItem('darkMode')) {
Chart.defaults.global.defaultFontColor = "#ffffff"
Chart.defaults.scale.gridLines.color = "#ffffff"
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a way to change Chart.js colors in dark mode, but unfortunately it doesn't work without refreshing the page. I'm sorry I didn't check before posting the suggested change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, it's still useful - there might be a way to reload the chart without reloading the entire page.

new Vue({
i18n,
Expand Down
7 changes: 7 additions & 0 deletions admin_ui/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 Chart from './views/Chart.vue'

Vue.use(Router)

Expand Down Expand Up @@ -37,6 +38,12 @@ export default new Router({
component: AddForm,
props: true
},
{
path: '/charts/:chartSlug',
name: 'chart',
component: Chart,
props: true
},
{
path: '/:tableName/',
name: 'rowListing',
Expand Down
15 changes: 15 additions & 0 deletions admin_ui/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default new Vuex.Store({
tableNames: [],
tableGroups: {},
formConfigs: [] as i.FormConfig[],
chartConfigs: [] as i.ChartConfig[],
user: undefined,
loadingStatus: false,
customLinks: {}
Expand Down Expand Up @@ -63,6 +64,9 @@ export default new Vuex.Store({
updateFormSchema(state, formSchema) {
state.formSchema = formSchema
},
updateChartConfigs(state, value) {
state.chartConfigs = value
},
updateApiResponseMessage(state, message: i.APIResponseMessage) {
state.apiResponseMessage = message
},
Expand Down Expand Up @@ -117,6 +121,17 @@ export default new Vuex.Store({
context.commit("updateFormSchema", response.data)
return response
},
async fetchChartConfigs(context) {
const response = await axios.get(`${BASE_URL}charts/`)
context.commit("updateChartConfigs", response.data)
},
async fetchChartConfig(context, chartSlug: string) {
const response = await axios.get(
`${BASE_URL}charts/${chartSlug}/`
)
context.commit("updateChartConfigs", response.data)
return response
},

/*********************************************************************/

Expand Down
29 changes: 29 additions & 0 deletions admin_ui/src/views/Chart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<DetailViewBase>
<ChartsPage :chartSlug="chartSlug" />
</DetailViewBase>
</template>


<script lang="ts">
import Vue from "vue"
import ChartsPage from "../components/ChartsPage.vue"
import DetailViewBase from "../components/DetailViewBase.vue"

export default Vue.extend({
props: {
chartSlug: String
},
components: {
ChartsPage,
DetailViewBase
},
async mounted() {
await this.$store.dispatch("fetchChartConfig", this.chartSlug)
}
})
</script>


<style scoped lang="less">
</style>
43 changes: 43 additions & 0 deletions docs/source/charts/examples/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import asyncio

from fastapi import FastAPI
from fastapi.routing import Mount
from home.tables import Director, Movie

from piccolo_admin.endpoints import ChartConfig, create_admin


# count directors movies
async def director_movie_count():
movies = await Movie.select(
Movie.director.name.as_alias("director"),
Count(Movie.id),
).group_by(Movie.director)
# Flatten the response so it's a list of lists
# like [['George Lucas', 3], ...]
return [[i["director"], i["count"]] for i in movies]


chart_data = asyncio.run(director_movie_count())


director_chart = ChartConfig(
title="Movie count",
chart_type="Pie",
data=chart_data,
)
Fixed Show fixed Hide fixed


app = FastAPI(
routes=[
Mount(
"/admin/",
create_admin(
tables=[Director, Movie],
charts=[director_chart],
),
),
],
)

# For Starlette it is identical, just `app = Starlette(...)`
9 changes: 9 additions & 0 deletions docs/source/charts/examples/home/tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from piccolo.columns.column_types import Varchar, ForeignKey
from piccolo.table import Table

class Director(Table):
name = Varchar()

class Movie(Table):
title = Varchar()
director = ForeignKey(references=Director)
Binary file added docs/source/charts/images/chart.png
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I noticed that the image in the docs is wrong (use Highcharts and not Chart.js), so if you can change that too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sinisaos OK will change that

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/charts/images/charts_sidebar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions docs/source/charts/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.. _Charts:

Charts
======

Piccolo Admin can display different types of charts based on your
data. Five chart types are supported: ``Pie``, ``Line``, ``Column``,
``Bar`` and ``Area``.

Here's an example of charts usage, using FastAPI:

.. literalinclude:: ./examples/app.py

Piccolo Admin will then show a chart in the UI.

.. image:: ./images/charts_sidebar.png

.. image:: ./images/chart.png

.. hint::

The data format must be a list of lists
(e.g. ``[["Male", 7], ["Female", 3]]``).

-------------------------------------------------------------------------------

Source
------

.. currentmodule:: piccolo_admin.endpoints

.. autoclass:: ChartConfig
Loading