Add support for dates and increase robustness

This commit is contained in:
Andrew Kingston 2020-11-04 12:43:56 +00:00
parent ac40abac56
commit e7eab46435
7 changed files with 132 additions and 68 deletions

View File

@ -452,6 +452,13 @@ export default {
dependsOn: "datasource", dependsOn: "datasource",
control: MultiTableViewFieldSelect, control: MultiTableViewFieldSelect,
}, },
{
label: "Format",
key: "yAxisUnits",
control: OptionSelect,
options: ["Default", "Thousands", "Millions"],
defaultValue: "Default",
},
{ {
label: "Y Axis Label", label: "Y Axis Label",
key: "yAxisLabel", key: "yAxisLabel",
@ -486,12 +493,6 @@ export default {
valueKey: "checked", valueKey: "checked",
defaultValue: false, defaultValue: false,
}, },
{
label: "Horizontal",
key: "horizontal",
control: Checkbox,
valueKey: "checked",
},
{ {
label: "Data Labels", label: "Data Labels",
key: "dataLabels", key: "dataLabels",

View File

@ -413,7 +413,6 @@
"default": "400" "default": "400"
}, },
"width": "number", "width": "number",
"horizontal": "bool",
"dataLabels": "bool", "dataLabels": "bool",
"animate": { "animate": {
"type": "bool", "type": "bool",
@ -422,7 +421,14 @@
"xAxisLabel": "string", "xAxisLabel": "string",
"yAxisLabel": "string", "yAxisLabel": "string",
"legend": "bool", "legend": "bool",
"stacked": "bool" "stacked": "bool",
"yAxisUnits": {
"type": "options",
"default": "Default",
"options": [
"Default", "Thousands", "Millions"
]
}
} }
}, },
"line": { "line": {

View File

@ -4,7 +4,11 @@
export let options export let options
</script> </script>
<div use:chart={options} /> {#if options}
<div use:chart={options} />
{:else}
<div>Invalid chart options</div>
{/if}
<style> <style>
div :global(.apexcharts-legend-series) { div :global(.apexcharts-legend-series) {

View File

@ -1,3 +1,5 @@
import { labelColumn, valueColumns } from "./BarChart.svelte"
export class ApexOptionsBuilder { export class ApexOptionsBuilder {
formatters = { formatters = {
["Default"]: val => Math.round(val * 100) / 100, ["Default"]: val => Math.round(val * 100) / 100,
@ -136,4 +138,8 @@ export class ApexOptionsBuilder {
this.formatters[units || "Default"] this.formatters[units || "Default"]
) )
} }
xType(type) {
return this.setOption(["xaxis", "type"], type)
}
} }

View File

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import fetchData from "../fetchData" import fetchData, { fetchSchema } from "../fetchData"
import { isEmpty, sortBy } from "lodash/fp" import { sortBy } from "lodash/fp"
import { ApexOptionsBuilder } from "./ApexOptionsBuilder" import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte" import ApexChart from "./ApexChart.svelte"
@ -14,55 +14,80 @@
export let height export let height
export let width export let width
export let color export let color
export let horizontal
export let dataLabels export let dataLabels
export let animate export let animate
export let legend export let legend
export let stacked export let stacked
export let yAxisUnits
let data let options
$: options = getChartOptions(data)
// Fetch data on mount // Fetch data on mount
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!datasource || !labelColumn || !valueColumns || !valueColumns.length) {
const result = (await fetchData(datasource)).slice(0, 20) return
data = sortBy(row => row[labelColumn])(result) }
const result = (await fetchData(datasource)).slice(0, 20)
const data = sortBy(row => row[labelColumn])(result)
const schema = await fetchSchema(datasource.tableId)
if (!schema || !data || !data.length) {
return
}
// Check columns are valid
if (datasource.type !== "view") {
if (schema[labelColumn] == null) {
return
}
for (let i = 0; i < valueColumns.length; i++) {
if (schema[valueColumns[i]] == null) {
return
}
}
} }
})
function getChartOptions(rows = []) {
// Initialise default chart // Initialise default chart
let builder = new ApexOptionsBuilder() let builder = new ApexOptionsBuilder()
.title(title)
.type("bar") .type("bar")
.title(title)
.width(width) .width(width)
.height(height) .height(height)
.xLabel(xAxisLabel) .xLabel(xAxisLabel)
.yLabel(yAxisLabel) .yLabel(yAxisLabel)
.horizontal(horizontal)
.dataLabels(dataLabels) .dataLabels(dataLabels)
.animate(animate) .animate(animate)
.legend(legend) .legend(legend)
.stacked(stacked) .stacked(stacked)
.yUnits(yAxisUnits)
// Add data if valid datasource // Add data
if (rows && rows.length) { let useDates = false
if (valueColumns && valueColumns.length) { if (datasource.type !== "view" && schema[labelColumn]) {
const labelFieldType = schema[labelColumn].type
builder = builder.xType(labelFieldType)
useDates = labelFieldType === "datetime"
}
const series = valueColumns.map(column => ({ const series = valueColumns.map(column => ({
name: column, name: column,
data: rows.map(row => parseFloat(row[column])), data: data.map(row => {
if (!useDates) {
return row[column]
} else {
return [row[labelColumn], row[column]]
}
}),
})) }))
builder = builder.series(series) builder = builder.series(series)
} if (!useDates && data[0][labelColumn] != null) {
if (!isEmpty(rows[0][labelColumn])) { builder = builder.categories(data.map(row => row[labelColumn]))
builder = builder.categories(rows.map(row => row[labelColumn]))
}
} }
// Build chart options // Build chart options
return builder.getOptions() options = builder.getOptions()
} })
$: console.log(options)
</script> </script>
<ApexChart {options} /> <ApexChart {options} />

View File

@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import fetchData from "../fetchData" import fetchData, { fetchSchema } from "../fetchData"
import { isEmpty, sortBy } from "lodash/fp" import { sortBy } from "lodash/fp"
import { ApexOptionsBuilder } from "./ApexOptionsBuilder" import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
import ApexChart from "./ApexChart.svelte" import ApexChart from "./ApexChart.svelte"
@ -26,23 +26,37 @@
export let stacked export let stacked
export let gradient export let gradient
let data = [] let options
$: options = getChartOptions(data)
// Fetch data on mount // Fetch data on mount
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!datasource || !labelColumn || !valueColumns || !valueColumns.length) {
const result = (await fetchData(datasource)).slice(0, 20) return
data = sortBy(row => row[labelColumn])(result) }
const result = (await fetchData(datasource)).slice(0, 100)
const data = sortBy(row => row[labelColumn])(result)
const schema = await fetchSchema(datasource.tableId)
if (!schema || !data || !data.length) {
return
}
// Check columns are valid
if (datasource.type !== "view") {
if (schema[labelColumn] == null) {
return
}
for (let i = 0; i < valueColumns.length; i++) {
if (schema[valueColumns[i]] == null) {
return
}
}
} }
})
function getChartOptions(rows = []) {
// Initialise default chart // Initialise default chart
let builder = new ApexOptionsBuilder() let builder = new ApexOptionsBuilder()
.title(title) .title(title)
.type(area ? "area" : "line") .type(area ? "area" : "line")
// .color(color)
.width(width) .width(width)
.height(height) .height(height)
.xLabel(xAxisLabel) .xLabel(xAxisLabel)
@ -55,23 +69,31 @@
.legend(legend) .legend(legend)
.yUnits(yAxisUnits) .yUnits(yAxisUnits)
// Add data if valid datasource // Add data
if (rows && rows.length) { let useDates = false
if (valueColumns && valueColumns.length) { if (datasource.type !== "view" && schema[labelColumn]) {
const labelFieldType = schema[labelColumn].type
builder = builder.xType(labelFieldType)
useDates = labelFieldType === "datetime"
}
const series = valueColumns.map(column => ({ const series = valueColumns.map(column => ({
name: column, name: column,
data: rows.map(row => parseFloat(row[column])), data: data.map(row => {
if (!useDates) {
return row[column]
} else {
return [row[labelColumn], row[column]]
}
}),
})) }))
builder = builder.series(series) builder = builder.series(series)
} if (!useDates && schema[labelColumn]) {
if (!isEmpty(rows[0][labelColumn])) { builder = builder.categories(data.map(row => row[labelColumn]))
builder = builder.categories(rows.map(row => row[labelColumn]))
}
} }
// Build chart options // Build chart options
return builder.getOptions() options = builder.getOptions()
} })
</script> </script>
<ApexChart {options} /> <ApexChart {options} />

View File

@ -15,11 +15,11 @@ export default async function fetchData(datasource, store) {
// Fetch table schema so we can check for linked rows // Fetch table schema so we can check for linked rows
if (rows && rows.length) { if (rows && rows.length) {
const table = await fetchTable() const schema = await fetchSchema(datasource.tableId)
const keys = Object.keys(table.schema) const keys = Object.keys(schema)
rows.forEach(row => { rows.forEach(row => {
for (let key of keys) { for (let key of keys) {
const type = table.schema[key].type const type = schema[key].type
if (type === "link") { if (type === "link") {
row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0 row[`${key}_count`] = Array.isArray(row[key]) ? row[key].length : 0
} else if (type === "attachment") { } else if (type === "attachment") {
@ -38,12 +38,6 @@ export default async function fetchData(datasource, store) {
return [] return []
} }
async function fetchTable() {
const FETCH_TABLE_URL = `/api/tables/${datasource.tableId}`
const response = await api.get(FETCH_TABLE_URL)
return await response.json()
}
async function fetchTableData() { async function fetchTableData() {
if (!name.startsWith("all_")) { if (!name.startsWith("all_")) {
throw new Error("Incorrect table convention - must begin with all_") throw new Error("Incorrect table convention - must begin with all_")
@ -85,3 +79,9 @@ export default async function fetchData(datasource, store) {
return row[datasource.fieldName] return row[datasource.fieldName]
} }
} }
export async function fetchSchema(id) {
const FETCH_TABLE_URL = `/api/tables/${id}`
const response = await api.get(FETCH_TABLE_URL)
return (await response.json()).schema
}