Add support for dates and increase robustness
This commit is contained in:
parent
ac40abac56
commit
e7eab46435
|
@ -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",
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue