Compare commits
No commits in common. "3dCharts" and "master" have entirely different histories.
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: [joshuashoemaker]
|
||||
15
README.md
@ -0,0 +1,15 @@
|
||||
# <img alt="data lovelace logo" src="./public/favicon.png" width="30px"/> [dataLovelace.app](https://datalovelace.app)
|
||||
|
||||
[💛 Support this Project 💛](https://github.com/sponsors/joshuashoemaker/)
|
||||
|
||||
Lovelace is a simple to use web application that puts the power of data reporting into the hands of those who need it for free. It is a Free and Open Source Software (FOSS) project.
|
||||
|
||||
With Lovelace you can take all of your varying data sources, aggregate them to one place, and perform what ever kind of reports on them you need to either create beautiful chart representations, or robust tabular exports of your data.
|
||||
|
||||

|
||||
|
||||
### [Read the Documentation](https://docs.datalovelace.app)
|
||||
|
||||
or
|
||||
|
||||
### [Jump Right In](https://datalovelace.app) and play with the tool yourself
|
||||
BIN
docs/graphExample.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
73
package-lock.json
generated
@ -3269,6 +3269,23 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
@ -4080,6 +4097,15 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
|
||||
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
|
||||
"requires": {
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6"
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@ -6775,21 +6801,21 @@
|
||||
"integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz",
|
||||
"integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
"setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -10786,6 +10812,23 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -11776,6 +11819,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
{
|
||||
"name": "lovelace.technology",
|
||||
"name": "datalovelace",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"chart.js": "^2.9.3",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"downloadjs": "^1.4.7",
|
||||
"express": "^4.17.1",
|
||||
"http-errors": "^1.8.0",
|
||||
"js2excel": "^1.0.1",
|
||||
"lovelacejs": "^0.2.1",
|
||||
"react": "^16.13.1",
|
||||
@ -21,6 +26,7 @@
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"server": "node ./server/index",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
BIN
public/favicon.png
Normal file
|
After Width: | Height: | Size: 167 KiB |
@ -2,12 +2,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="With Lovelace you can take all of your varying data sources, aggregate them to one place, and perform what ever kind of reports on them you need to either create beautiful chart representations, or a robust tabular export of your data."
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "Lovelace",
|
||||
"name": "datalovelace.app",
|
||||
"description": "With Lovelace you can take all of your varying data sources, aggregate them to one place, and perform what ever kind of reports on them you need to either create beautiful chart representations, or a robust tabular export of your data.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"src": "favicon.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"src": "favicon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"src": "favicon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
|
||||
61
server/Server.js
Normal file
@ -0,0 +1,61 @@
|
||||
import express from 'express'
|
||||
import createError from 'http-errors'
|
||||
import path from 'path'
|
||||
import bodyParser from 'body-parser'
|
||||
import cookieParser from 'cookie-parser'
|
||||
|
||||
let instance = null
|
||||
|
||||
class Server {
|
||||
constructor () {
|
||||
if (!instance) { instance = this }
|
||||
|
||||
this.createApp()
|
||||
this.setupAppOptions()
|
||||
this.setupAppRoutes()
|
||||
this.setupAppErrors()
|
||||
return instance
|
||||
}
|
||||
|
||||
createApp = () => {
|
||||
this.app = express()
|
||||
this.app.use(express.json())
|
||||
this.app.use(express.urlencoded({ extended: false }))
|
||||
this.app.use(cookieParser())
|
||||
this.app.use(express.static('./build'))
|
||||
this.app.use(bodyParser.json())
|
||||
|
||||
}
|
||||
|
||||
setupAppErrors = () => {
|
||||
this.app.use((request, response, next) => {
|
||||
next(createError(404))
|
||||
})
|
||||
|
||||
this.app.use((error, request, response, next) => {
|
||||
response.locals.message = error.message
|
||||
response.locals.error = request.app.get('env') === 'development' ? error : {}
|
||||
|
||||
response.status(error.status || 500)
|
||||
response.send('error')
|
||||
})
|
||||
}
|
||||
|
||||
setupAppRoutes = () => {
|
||||
const __dirname = path.resolve()
|
||||
this.app.get('*', (request, response, next) => {
|
||||
response.sendFile(path.join(__dirname, '/build/index.html'))
|
||||
})
|
||||
}
|
||||
|
||||
setupAppOptions = () => {
|
||||
this.app.use((request, response, next) => {
|
||||
response.header('Access-Control-Allow-Origin', request.headers.origin || '*')
|
||||
response.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,HEAD,DELETE,OPTIONS')
|
||||
response.header('Access-Control-Allow-Headers', 'Content-Type,x-requested-with')
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default Server
|
||||
29
server/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
import http from 'http'
|
||||
import Server from './Server.js'
|
||||
|
||||
const main = () => {
|
||||
|
||||
const port = normalizePort(process.env.PORT || '5003')
|
||||
const webService = createServer()
|
||||
|
||||
webService.listen(port, () => {
|
||||
console.log(`Server is listening on ${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
const createServer = () => {
|
||||
const server = new Server()
|
||||
return http.createServer(server.app)
|
||||
}
|
||||
|
||||
const normalizePort = portString => {
|
||||
const port = parseInt(portString, 10)
|
||||
|
||||
if (isNaN(port)) return portString
|
||||
else if (port >= 0 ) return port
|
||||
else return 0
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
export { main, createServer, normalizePort }
|
||||
@ -1,4 +1,5 @@
|
||||
export default {
|
||||
oneAxisCharts: ['line', 'bar', 'radar', 'doughnut', 'pie', 'polar'],
|
||||
twoAxisCharts: ['scatter'] /*'bubble', 'scatter', 'area', 'mixed' */
|
||||
twoAxisCharts: ['scatter'],
|
||||
threeAxisCharts: ['bubble'] /* 'mixed' */
|
||||
}
|
||||
|
||||
20
src/Models/Chart/Chart.js
vendored
@ -7,6 +7,8 @@ class Chart {
|
||||
this.table = props.table
|
||||
this.type = props.type
|
||||
this.reportValue = props.reportValue
|
||||
this.xAxis = props.xAxis
|
||||
this.yAxis = props.yAxis
|
||||
}
|
||||
|
||||
get id () {
|
||||
@ -38,6 +40,14 @@ class Chart {
|
||||
return this._reportValue
|
||||
}
|
||||
|
||||
get xAxis () {
|
||||
return this._xAxis
|
||||
}
|
||||
|
||||
get yAxis () {
|
||||
return this._yAxis
|
||||
}
|
||||
|
||||
set reportValue (value) {
|
||||
this._reportValue = value || this._reportValue
|
||||
return this._reportValue
|
||||
@ -56,6 +66,16 @@ class Chart {
|
||||
else this._chartType = null
|
||||
}
|
||||
|
||||
set xAxis (value) {
|
||||
this._xAxis = value
|
||||
return this._xAxis
|
||||
}
|
||||
|
||||
set yAxis (value) {
|
||||
this._yAxis = value
|
||||
return this._yAxis
|
||||
}
|
||||
|
||||
_validateChartType = type => {
|
||||
const allChartTypes = Object.values(chartTypes).flat()
|
||||
if (allChartTypes.includes(type)) return true
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { uuid } from 'uuidv4'
|
||||
import OneAxisChart from '../../Models/Chart/OneAxisChart'
|
||||
import TwoAxisChart from '../../Models/Chart/TwoAxisChart'
|
||||
import ThreeAxisChart from '../../Models/Chart/ThreeAxisChart'
|
||||
import chartTypes from '../../Constants/chartTypes'
|
||||
|
||||
let instance = null
|
||||
@ -16,6 +17,7 @@ class Charts {
|
||||
let newChart = null
|
||||
if (chartTypes.oneAxisCharts.includes(chart.type)) newChart = this._generateOneAxisChart(chart)
|
||||
if (chartTypes.twoAxisCharts.includes(chart.type)) newChart = this._generateTwoAxisChart(chart)
|
||||
if (chartTypes.threeAxisCharts.includes(chart.type)) newChart = this._generateThreeAxisChart(chart)
|
||||
|
||||
if (newChart) this.collection.push(newChart)
|
||||
}
|
||||
@ -66,6 +68,19 @@ class Charts {
|
||||
})
|
||||
return newChart
|
||||
}
|
||||
|
||||
_generateThreeAxisChart = chart => {
|
||||
const newChart = new ThreeAxisChart({
|
||||
id: chart.id || uuid(),
|
||||
label: chart.label,
|
||||
type: chart.type,
|
||||
table: chart.table,
|
||||
xAxis: chart.xAxis,
|
||||
yAxis: chart.yAxis,
|
||||
reportValue: chart.reportValue
|
||||
})
|
||||
return newChart
|
||||
}
|
||||
}
|
||||
|
||||
export default Charts
|
||||
|
||||
51
src/Models/Chart/ThreeAxisChart.js
Normal file
@ -0,0 +1,51 @@
|
||||
import Chart from './Chart.js'
|
||||
|
||||
class ThreeAxisChart extends Chart {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.chartSize = 600
|
||||
this.rows = props.table.export()
|
||||
this.scale = props.scale || 3.5
|
||||
|
||||
|
||||
const reportValues = this.rows.map(r => {
|
||||
return r[this.reportValue]
|
||||
})
|
||||
this.largestValue = Math.max(...reportValues)
|
||||
}
|
||||
|
||||
get bubble () {
|
||||
const data = this.rows.map(r => {
|
||||
return { x: r[this.xAxis], y: r[this.yAxis], r: this.calculateReportValueScale(r[this.reportValue]) }
|
||||
})
|
||||
|
||||
const pointColor = { r: this._generateRandomRGBNumber(), g: this._generateRandomRGBNumber(), b: this._generateRandomRGBNumber(), a: 0.2 }
|
||||
|
||||
return {
|
||||
labels: [this.xAxis, this.yAxis],
|
||||
datasets: [
|
||||
{
|
||||
label: this.reportValue,
|
||||
data: data,
|
||||
borderColor: `rgba(255, 255, 255, 0.3)`,
|
||||
backgroundColor: `rgba(${pointColor.r}, ${pointColor.g}, ${pointColor.b}, 0.6)`
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
calculateReportValueScale = (reportValue) => {
|
||||
const scale = 3.5
|
||||
const size = (reportValue / this.largestValue) * 100
|
||||
return Math.round(size / scale)
|
||||
}
|
||||
|
||||
_generateRandomRGBNumber () {
|
||||
const max = 255
|
||||
const min = 0
|
||||
return Math.round(Math.random() * (max - min) - min)
|
||||
}
|
||||
}
|
||||
|
||||
export default ThreeAxisChart
|
||||
@ -3,14 +3,11 @@ import Chart from './Chart'
|
||||
class TwoAxisChart extends Chart {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.xAxis = props.xAxis
|
||||
this.yAxis = props.yAxis
|
||||
this.rows = props.table.export()
|
||||
}
|
||||
|
||||
get scatter () {
|
||||
const rows = this.table.export()
|
||||
const data = rows.map(r => {
|
||||
const data = this.rows.map(r => {
|
||||
return { x: r[this.xAxis], y: r[this.yAxis] }
|
||||
})
|
||||
|
||||
|
||||
1
src/img/faq.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="16px" height="16px"><path fill="#f78f8f" d="M76.993,77.479c-4.54-0.181-9.738-1.562-14.762-3.935l-0.193-0.091L61.84,73.53 C58.411,74.837,54.764,75.5,51,75.5c-14.612,0-26.5-10.094-26.5-22.5S36.388,30.5,51,30.5S77.5,40.594,77.5,53 c0,5.191-2.134,10.258-6.009,14.266l-0.242,0.251L71.4,67.83C73.215,71.602,75.094,74.842,76.993,77.479z"/><path fill="#c74343" d="M51,31c14.336,0,26,9.869,26,22c0,5.061-2.084,10.004-5.869,13.918l-0.484,0.501l0.302,0.628 c1.637,3.402,3.327,6.377,5.04,8.873c-4.223-0.329-8.954-1.659-13.545-3.827l-0.385-0.182l-0.398,0.152 C58.29,74.348,54.703,75,51,75c-14.336,0-26-9.869-26-22S36.664,31,51,31 M51,30c-14.912,0-27,10.297-27,23 c0,12.703,12.088,23,27,23c3.926,0,7.652-0.72,11.017-2.003C67.105,76.4,72.814,78,78,78c-2.352-3.098-4.402-6.754-6.15-10.387 C75.692,63.64,78,58.55,78,53C78,40.297,65.912,30,51,30L51,30z"/><path fill="#fff" d="M61.534,62.538h-5.33l-1.545-4.83h-7.723l-1.529,4.83h-5.3l7.904-21.714h5.8L61.534,62.538z M53.539,53.952c0,0-2.625-8.495-2.695-9.252h-0.121c-0.05,0.636-2.741,9.252-2.741,9.252H53.539z"/><path fill="#8bb7f0" d="M3.007,49.479c1.899-2.638,3.778-5.878,5.593-9.649l0.151-0.313l-0.242-0.251 C4.634,35.258,2.5,30.191,2.5,25C2.5,12.594,14.388,2.5,29,2.5S55.5,12.594,55.5,25S43.612,47.5,29,47.5 c-3.764,0-7.411-0.663-10.84-1.97l-0.198-0.076l-0.193,0.091C12.745,47.918,7.547,49.299,3.007,49.479z"/><path fill="#4e7ab5" d="M29,3c14.336,0,26,9.869,26,22S43.336,47,29,47c-3.703,0-7.29-0.652-10.661-1.937l-0.398-0.152 l-0.385,0.182c-4.591,2.168-9.322,3.498-13.545,3.827c1.712-2.496,3.403-5.471,5.04-8.873l0.302-0.628l-0.484-0.501 C5.084,35.004,3,30.061,3,25C3,12.869,14.664,3,29,3 M29,2C14.088,2,2,12.297,2,25c0,5.55,2.308,10.64,6.15,14.613 C6.402,43.246,4.352,46.902,2,50c5.186,0,10.895-1.6,15.983-4.003C21.348,47.28,25.074,48,29,48c14.912,0,27-10.297,27-23 C56,12.297,43.912,2,29,2L29,2z"/><g><path fill="#fff" d="M28.534,35.965c-2.986,0-5.42-1.077-7.301-3.232c-1.881-2.176-2.822-4.993-2.822-8.452 c0-3.652,0.955-6.615,2.865-8.888c1.9-2.262,4.431-3.394,7.592-3.394c2.977,0,5.381,1.083,7.214,3.248 c1.842,2.155,2.763,5.01,2.763,8.565c0,3.631-0.955,6.566-2.865,8.807c-0.068,0.075-0.131,0.148-0.189,0.218 c-0.058,0.07-0.121,0.137-0.189,0.202l5.279,5.64h-6.574l-2.763-3.119C30.634,35.831,29.63,35.965,28.534,35.965z M28.738,16.493 c-1.639,0-2.948,0.689-3.927,2.068c-0.97,1.368-1.454,3.184-1.454,5.446c0,2.295,0.485,4.11,1.454,5.446 c0.97,1.336,2.24,2.004,3.811,2.004c1.619,0,2.904-0.646,3.854-1.939c0.95-1.314,1.425-3.119,1.425-5.414 c0-2.392-0.461-4.261-1.382-5.608C31.608,17.16,30.348,16.493,28.738,16.493z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
1
src/img/information.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="50px" height="50px"><circle cx="77" cy="13" r="1" fill="#f1bc19"/><circle cx="50" cy="50" r="37" fill="#e6edb7"/><circle cx="83" cy="15" r="4" fill="#f1bc19"/><circle cx="87" cy="24" r="2" fill="#88ae45"/><circle cx="81" cy="76" r="2" fill="#fbcd59"/><circle cx="15" cy="63" r="4" fill="#fbcd59"/><circle cx="25" cy="87" r="2" fill="#88ae45"/><circle cx="18.5" cy="51.5" r="2.5" fill="#fff"/><circle cx="79.5" cy="33.5" r="1.5" fill="#fff"/><g><path fill="#f4989e" d="M37.084,74.3c-0.212,0-0.384-0.172-0.384-0.384v-4.832c0-0.212,0.172-0.384,0.384-0.384h5.724 c1.044,0,1.893-0.849,1.893-1.893V46.192c0-1.044-0.849-1.893-1.893-1.893h-3.724c-0.212,0-0.384-0.172-0.384-0.384v-4.832 c0-0.212,0.172-0.384,0.384-0.384h16.832c0.212,0,0.384,0.172,0.384,0.384v27.724c0,1.044,0.849,1.893,1.893,1.893h6.724 c0.212,0,0.384,0.172,0.384,0.384v4.832c0,0.212-0.172,0.384-0.384,0.384H37.084z"/><path fill="#472b29" d="M55.6,39.4V41v4v21.808c0,1.429,1.163,2.592,2.593,2.592H64.6v4.2H37.4v-4.2h5.407 c1.43,0,2.593-1.163,2.593-2.592V46.192c0-1.429-1.163-2.592-2.593-2.592H39.4v-4.2H55.6 M55.916,38H39.084 C38.485,38,38,38.485,38,39.084v4.832C38,44.515,38.485,45,39.084,45h3.724C43.466,45,44,45.534,44,46.192v20.615 C44,67.466,43.466,68,42.807,68h-5.724C36.485,68,36,68.485,36,69.084v4.832C36,74.515,36.485,75,37.084,75h27.832 C65.515,75,66,74.515,66,73.916v-4.832C66,68.485,65.515,68,64.916,68h-6.724C57.534,68,57,67.466,57,66.808V45v-4v-1.916 C57,38.485,56.515,38,55.916,38L55.916,38z"/><g><ellipse cx="49.5" cy="27.5" fill="#f4989e" rx="6.8" ry="5.8"/><path fill="#472b29" d="M49.5,22.4c3.364,0,6.1,2.288,6.1,5.1s-2.736,5.1-6.1,5.1s-6.1-2.288-6.1-5.1 S46.136,22.4,49.5,22.4 M49.5,21c-4.142,0-7.5,2.91-7.5,6.5s3.358,6.5,7.5,6.5s7.5-2.91,7.5-6.5S53.642,21,49.5,21L49.5,21z"/></g></g><g><path fill="#472b29" d="M62.5,71.25h-5.292c-2.183,0-3.958-1.775-3.958-3.958V63c0-0.138,0.112-0.25,0.25-0.25 s0.25,0.112,0.25,0.25v4.292c0,1.907,1.551,3.458,3.458,3.458H62.5c0.138,0,0.25,0.112,0.25,0.25S62.638,71.25,62.5,71.25z"/></g><g><path fill="#472b29" d="M53.5,60.667c-0.138,0-0.25-0.112-0.25-0.25V41.75h-2.167c-0.138,0-0.25-0.112-0.25-0.25 s0.112-0.25,0.25-0.25h2.667v19.167C53.75,60.555,53.638,60.667,53.5,60.667z"/></g><g><path fill="#472b29" d="M49.167,41.75H46.25c-0.138,0-0.25-0.112-0.25-0.25s0.112-0.25,0.25-0.25h2.917 c0.138,0,0.25,0.112,0.25,0.25S49.305,41.75,49.167,41.75z"/></g><g><path fill="#472b29" d="M44.25,41.75H41.5c-0.138,0-0.25-0.112-0.25-0.25s0.112-0.25,0.25-0.25h2.75 c0.138,0,0.25,0.112,0.25,0.25S44.388,41.75,44.25,41.75z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
src/img/octocat.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#455A64" d="M40.3,15.7c0.6-1.7,1.2-5-0.4-8.7c-4.5,0-8.3,3.2-8.9,3.8c-2.2-0.5-4.6-0.7-7-0.7c-2.5,0-4.9,0.3-7.2,0.8C13.7,7.7,9.6,7,8,7c0,0-0.9,1.8-0.9,5c0,2,0.5,3.2,0.8,3.8C5.5,18.3,4,21.7,4,26.1c0,11.2,7.1,15,20,15s20-3.8,20-15C44,21.5,42.6,18.1,40.3,15.7z"/><path fill="#FFCCBC" d="M24,39c-8.2,0-15-1.4-15-9c0-2.9,1.6-4.5,2.7-5.5c2.5-2.2,6.7-1.2,12.3-1.2c4.1,0,7.6-0.7,10.4,0.2c2.8,0.9,4.6,3.5,4.6,6.3C39,37.7,35,39,24,39z"/><path fill="#D84315" d="M25,34c0,0.6-0.4,1-1,1s-1-0.4-1-1s0.4-1,1-1S25,33.4,25,34z M26.5,36.5c0.2-0.2,0.2-0.5,0-0.7s-0.5-0.2-0.7,0c-0.9,0.9-2.6,0.9-3.5,0c-0.2-0.2-0.5-0.2-0.7,0s-0.2,0.5,0,0.7c0.7,0.7,1.5,1,2.5,1S25.8,37.1,26.5,36.5z"/><path fill="#FFF" d="M19,29.5c0,2.5-1.3,4.5-3,4.5s-3-2-3-4.5s1.3-4.5,3-4.5S19,27,19,29.5z M32,25c-1.7,0-3,2-3,4.5s1.3,4.5,3,4.5c1.7,0,3-2,3-4.5S33.7,25,32,25z"/><path fill="#6D4C41" d="M34,30c0,1.7-0.9,3-2,3s-2-1.3-2-3c0-0.2,0-0.5,0.1-0.7c0.1,0.4,0.5,0.7,0.9,0.7c0.6,0,1-0.4,1-1c0-0.6-0.4-1-1-1c-0.2,0-0.4,0.1-0.6,0.2c0.4-0.7,0.9-1.2,1.6-1.2C33.1,27,34,28.3,34,30z M16,27c-0.7,0-1.2,0.5-1.6,1.2c0.2-0.1,0.4-0.2,0.6-0.2c0.6,0,1,0.4,1,1c0,0.6-0.4,1-1,1c-0.4,0-0.8-0.3-0.9-0.7c0,0.2-0.1,0.5-0.1,0.7c0,1.7,0.9,3,2,3s2-1.3,2-3S17.1,27,16,27z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/img/sponsor.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="16px" height="16px"><path fill="#efd476" d="M39.1,69.3l23.4-11.7c2.6-1.3,4.2-4.6,3.2-7.4c-0.9-2.3-3.1-3.7-5.4-3.7c-0.7,0-1.5,0.1-2.2,0.4 L39,54.8"/><path fill="#c59b3f" d="M39.4,69.8L39,68.9l23.4-11.7c2.5-1.2,3.8-4.3,2.9-6.7c-0.8-2.1-2.7-3.4-4.9-3.4 c-0.7,0-1.4,0.1-2,0.4l-19.1,7.9l-0.4-0.9L58,46.6c0.8-0.3,1.6-0.5,2.4-0.5c2.6,0,4.9,1.6,5.8,4c1.1,2.9-0.5,6.6-3.4,8L39.4,69.8z"/><path fill="#efd476" d="M45.1,71.3l23.4-11.7c2.6-1.3,4.2-4.6,3.2-7.4c-0.9-2.3-3.1-3.7-5.4-3.7c-0.7,0-1.5,0.1-2.2,0.4 L45,56.8"/><path fill="#c59b3f" d="M45.4,71.8L45,70.9l23.4-11.7c2.5-1.2,3.8-4.3,2.9-6.7c-0.8-2.1-2.7-3.4-4.9-3.4 c-0.7,0-1.4,0.1-2,0.4l-19.1,7.9l-0.4-0.9L64,48.6c0.8-0.3,1.6-0.5,2.4-0.5c2.6,0,4.9,1.6,5.8,4c1.1,2.9-0.5,6.6-3.4,8L45.4,71.8z"/><path fill="#ffee9f" d="M48.2,73.5c-0.9,0-1.7-0.2-2.5-0.6L15.6,59.1L2.5,64.4V43.7l11.1-4.1c2-0.7,4-1.1,6.1-1.1 c2.9,0,5.7,0.7,8.2,2l19.1,10c2.6,1.4,3.6,4.6,2.3,7.2l-0.2,0.4l1.7,1.2l19.4-8c0.6-0.3,1.3-0.4,2-0.4c2.2,0,4.1,1.3,4.9,3.4 c0.9,2.4-0.4,5.5-2.9,6.7L50.9,72.9C50.1,73.3,49.1,73.5,48.2,73.5z"/><path fill="#c59b3f" d="M19.8,39c2.8,0,5.5,0.7,8,2l19.1,10c2.4,1.2,3.3,4.2,2.1,6.6l-0.4,0.8l0.7,0.5l1.2,0.8l0.4,0.3 l0.5-0.2l19.2-7.9c0.6-0.2,1.2-0.4,1.8-0.4c2,0,3.7,1.2,4.4,3.1c0.8,2.2-0.4,5-2.7,6.1L50.7,72.4c-0.8,0.4-1.6,0.6-2.5,0.6 c-0.8,0-1.6-0.2-2.3-0.5L16,58.8l-0.4-0.2l-0.4,0.2L3,63.6V44.1l10.8-4C15.7,39.4,17.7,39,19.8,39 M19.8,38c-2.1,0-4.3,0.4-6.3,1.1 L2,43.4v21.7l13.6-5.4l29.9,13.7c0.9,0.4,1.8,0.6,2.7,0.6c1,0,2-0.2,2.9-0.7l23.4-11.7c2.6-1.3,4.2-4.6,3.2-7.4 c-0.9-2.3-3.1-3.7-5.4-3.7c-0.7,0-1.5,0.1-2.2,0.4L51,58.8L49.8,58c1.5-2.9,0.3-6.4-2.5-7.9l-19.1-10C25.6,38.7,22.7,38,19.8,38 L19.8,38z"/><path fill="#c59b3f" d="M45.7,60.6c-0.7,0-1.4-0.2-2-0.5l-12.3-6.2l0.5-0.9l12.3,6.2c1.6,0.8,3.7,0.2,4.6-1.4l0.9,0.5 C48.8,59.8,47.3,60.6,45.7,60.6z"/><g><path fill="#ffee9f" d="M3.7 43.8L2 44.5 2 64 3.7 63.4z"/></g><g><path fill="#ff8f8f" d="M48,37.394C45.74,35.831,30.5,24.95,30.5,16c0-5.505,3.866-9.5,9.192-9.5 c3.238,0,5.678,1.326,7.907,4.3l0.4,0.534l0.4-0.534c2.229-2.974,4.669-4.3,7.907-4.3c5.326,0,9.192,3.995,9.192,9.5 C65.5,24.953,50.261,35.831,48,37.394z"/><path fill="#e54141" d="M56.308,7C61.344,7,65,10.785,65,16c0,8.395-14.013,18.687-17,20.785C45.011,34.687,31,24.401,31,16 c0-5.215,3.656-9,8.692-9c3.107,0,5.353,1.226,7.508,4.1l0.8,1.067l0.8-1.067C50.955,8.226,53.201,7,56.308,7 M56.308,6 C52.776,6,50.25,7.5,48,10.5c-2.25-3-4.776-4.5-8.308-4.5C34.339,6,30,10,30,16c0,10,18,22,18,22s18-12,18-22 C66,10,61.661,6,56.308,6L56.308,6z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import './App.css'
|
||||
import 'semantic-ui-css/semantic.min.css'
|
||||
|
||||
import DataTable from './DataTable/DataTable'
|
||||
import ListViewer from './ListViewer/ListViewer'
|
||||
import Nav from './Nav/Nav'
|
||||
|
||||
7
src/views/ChartViewer/ChartViewer.css
Normal file
@ -0,0 +1,7 @@
|
||||
.chartLabel {
|
||||
text-align: center;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.saveChartAsImageButton {
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import FocusChart from '../../Models/Chart/FocusChart'
|
||||
import { Doughnut, Bar, Line, Pie, Polar, Radar, Scatter } from 'react-chartjs-2'
|
||||
import { Doughnut, Bar, Line, Pie, Polar, Radar, Scatter, Bubble } from 'react-chartjs-2'
|
||||
import { Button } from 'semantic-ui-react'
|
||||
import download from 'downloadjs'
|
||||
import './ChartViewer.css'
|
||||
|
||||
class ChartViewer extends Component {
|
||||
constructor () {
|
||||
@ -17,31 +18,31 @@ class ChartViewer extends Component {
|
||||
document.addEventListener('setSelectedChart', this.setFocusChart)
|
||||
}
|
||||
|
||||
closeChart = () => {
|
||||
this.focusChart.chart = null
|
||||
this.setFocusChart()
|
||||
}
|
||||
|
||||
handleGroupByChange = (e, value) => {
|
||||
this.setState({ reportValue: value.value })
|
||||
}
|
||||
|
||||
saveChart = () => {
|
||||
console.log(this.chart.current)
|
||||
const base64OfChart = this.chart.current.chartInstance.toBase64Image()
|
||||
download(base64OfChart, this.focusChart.chart.label, 'image/png')
|
||||
}
|
||||
|
||||
setFocusChart = () => {
|
||||
const focusChart = this.focusChart.chart
|
||||
if (focusChart) {
|
||||
this.setState({
|
||||
chart: focusChart
|
||||
})
|
||||
}
|
||||
this.setState({
|
||||
chart: focusChart
|
||||
})
|
||||
}
|
||||
|
||||
renderChart = () => {
|
||||
const { chart } = this.state
|
||||
if (!chart) return
|
||||
|
||||
console.log(chart)
|
||||
|
||||
if (chart.type === 'bar') return <Bar data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
if (chart.type === 'doughnut') return <Doughnut data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
if (chart.type === 'line') return <Line data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
@ -49,14 +50,18 @@ class ChartViewer extends Component {
|
||||
if (chart.type === 'polar') return <Polar data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
if (chart.type === 'radar') return <Radar data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
if (chart.type === 'scatter') return <Scatter data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
if (chart.type === 'bubble') return <Bubble data={chart[chart.type]} width={600} height={600} ref={this.chart} />
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const chart = this.state.chart || {}
|
||||
return (
|
||||
<div className='ChartViewer'>
|
||||
<h3 className='chartLabel'>{ chart.label }</h3>
|
||||
{this.renderChart()}
|
||||
<br />
|
||||
{ this.state.chart ? <Button fluid onClick={this.saveChart}>Save As Image</Button> : '' }
|
||||
{ this.state.chart ? <Button className='saveChartAsImageButton' fluid primary onClick={this.saveChart}>Save As Image</Button> : '' }
|
||||
{ this.state.chart ? <Button className='closeChartButton' fluid onClick={this.closeChart}>Close Chart</Button> : '' }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ class CreateChartForm extends Component {
|
||||
}
|
||||
|
||||
renderConfigOptions = () => {
|
||||
const { oneAxisCharts, twoAxisCharts } = chartTypes
|
||||
const { oneAxisCharts, twoAxisCharts, threeAxisCharts } = chartTypes
|
||||
const { chartType } = this.state
|
||||
|
||||
let configElements = []
|
||||
@ -148,6 +148,41 @@ class CreateChartForm extends Component {
|
||||
/>
|
||||
)
|
||||
}
|
||||
else if (threeAxisCharts.includes(chartType)) {
|
||||
configElements.push(
|
||||
<Dropdown
|
||||
value ={this.state.xAxisValue}
|
||||
placeholder='X-Axis'
|
||||
options={this.getHeaderDropDownOptions()}
|
||||
fluid
|
||||
selection
|
||||
style={{ width: '300px' }}
|
||||
onChange={this.handleXAxisChange}
|
||||
/>
|
||||
)
|
||||
configElements.push(
|
||||
<Dropdown
|
||||
value ={this.state.yAxisValue}
|
||||
placeholder='Y-Axis'
|
||||
options={this.getHeaderDropDownOptions()}
|
||||
fluid
|
||||
selection
|
||||
style={{ width: '300px' }}
|
||||
onChange={this.handleYAxisChange}
|
||||
/>
|
||||
)
|
||||
configElements.push(
|
||||
<Dropdown
|
||||
value ={this.state.reportValue}
|
||||
placeholder='Report By'
|
||||
options={this.getHeaderDropDownOptions()}
|
||||
fluid
|
||||
selection
|
||||
style={{ width: '300px' }}
|
||||
onChange={this.handleReportValueChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return configElements
|
||||
}
|
||||
|
||||
|
||||
@ -1,15 +1,19 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Input, Dropdown, Grid, Button, Icon, List } from 'semantic-ui-react'
|
||||
import Tables from '../../Models/Tables'
|
||||
import './CreateNodule.css'
|
||||
|
||||
class CreateFilterNoduleForm extends Component {
|
||||
constructor () {
|
||||
super()
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
filterType: '',
|
||||
filterParams: {}
|
||||
filterParams: {},
|
||||
tables: props.tables
|
||||
}
|
||||
|
||||
this.tables = new Tables()
|
||||
|
||||
this.keyInput = React.createRef()
|
||||
this.valueInput = React.createRef()
|
||||
document.addEventListener('updateTables', this.updateTableList)
|
||||
@ -18,21 +22,44 @@ class CreateFilterNoduleForm extends Component {
|
||||
addKeyValueInput = () => {
|
||||
let filterParams = this.state.filterParams || {}
|
||||
|
||||
if (this.keyInput.current.inputRef.current.value)
|
||||
filterParams[this.keyInput.current.inputRef.current.value] = this.valueInput.current.inputRef.current.value
|
||||
if (this.state.keyValue)
|
||||
filterParams[this.state.keyValue] = this.valueInput.current.inputRef.current.value
|
||||
|
||||
this.setState({ filterParams: filterParams })
|
||||
}
|
||||
|
||||
handleChange = (e, value) => {
|
||||
componentWillReceiveProps = nextProps => {
|
||||
this.setState({tables: nextProps.tables})
|
||||
}
|
||||
|
||||
handleComparisonChange = (e, value) => {
|
||||
this.setState({ filterType: value.value })
|
||||
}
|
||||
|
||||
handleKeyChange = (e, value) => {
|
||||
this.setState({ keyValue: value.value })
|
||||
}
|
||||
|
||||
getFilterProperties = () => {
|
||||
const { filterType, filterParams } = this.state
|
||||
return { filterType, filterParams }
|
||||
}
|
||||
|
||||
getHeadersDropDownOptions = () => {
|
||||
const { tables } = this.state
|
||||
if (!tables || tables.length === 0) return []
|
||||
|
||||
const headers = tables.map(t => {
|
||||
const table = this.tables.getTableByLabel(t)
|
||||
return table.headers
|
||||
}).flat()
|
||||
|
||||
const dropdownOptions = headers.map(h => {
|
||||
return { key: h, text: h, value: h }
|
||||
})
|
||||
return dropdownOptions
|
||||
}
|
||||
|
||||
renderFilterParams = () => {
|
||||
const { filterParams } = this.state
|
||||
|
||||
@ -61,7 +88,7 @@ class CreateFilterNoduleForm extends Component {
|
||||
{key: 'LESSER', text: 'LESSER THAN', value: 'LESSER'},
|
||||
{key: 'LESSEREQUAL', text: 'LESSER THEN EQUAL TO', value: 'LESSEREQUAL'}
|
||||
]}
|
||||
onChange={this.handleChange}
|
||||
onChange={this.handleComparisonChange}
|
||||
/>
|
||||
|
||||
<Grid columns={2} stackable>
|
||||
@ -69,7 +96,18 @@ class CreateFilterNoduleForm extends Component {
|
||||
<List celled>
|
||||
{ filterParamElements.filterKeyElements }
|
||||
</List>
|
||||
<Input placeholder='Key' ref={this.keyInput} fluid />
|
||||
|
||||
<Dropdown
|
||||
clearable
|
||||
search
|
||||
header='Headers'
|
||||
placeholder='Header Key'
|
||||
fluid
|
||||
selection
|
||||
options={this.getHeadersDropDownOptions()}
|
||||
onChange={this.handleKeyChange}
|
||||
/>
|
||||
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<List celled>
|
||||
|
||||
@ -156,9 +156,8 @@ class CreateJoinNoduleForm extends Component {
|
||||
options={this.getForeignHeadersDropDownOptions()}
|
||||
onChange={this.handleForeignKeyChange}
|
||||
/>
|
||||
<br />
|
||||
|
||||
<Button animated='vertical' onClick={this.addJoinParam}>
|
||||
<Button fluid animated='vertical' onClick={this.addJoinParam}>
|
||||
<Button.Content hidden><Icon name='add' /></Button.Content>
|
||||
<Button.Content visible>Add</Button.Content>
|
||||
</Button>
|
||||
|
||||
@ -15,7 +15,8 @@ class CreateNodule extends Component {
|
||||
|
||||
this.state = {
|
||||
noduleType: '',
|
||||
tablesToImportByLabel: []
|
||||
tablesToImportByLabel: [],
|
||||
selectedTablesLabels: []
|
||||
}
|
||||
|
||||
this.tables = new Tables()
|
||||
@ -32,13 +33,18 @@ class CreateNodule extends Component {
|
||||
|
||||
clearInput = () => {
|
||||
this.setState({ noduleType: '' })
|
||||
this.noduleLabelInput.current.inputRef.current.value = ''
|
||||
this.tableSelect.current.clearTablesSelected()
|
||||
}
|
||||
|
||||
handleChange = (e, value) => {
|
||||
handleNoduleTypeChange = (e, value) => {
|
||||
this.setState({ noduleType: value.value })
|
||||
}
|
||||
|
||||
handleSelectedTablesChange = labels => {
|
||||
this.setState({ selectedTablesLabels: labels })
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
const { noduleType } = this.state
|
||||
|
||||
@ -79,11 +85,12 @@ class CreateNodule extends Component {
|
||||
}
|
||||
|
||||
renderNoduleForm = () => {
|
||||
const { noduleType, tablesToImportByLabel } = this.state
|
||||
const { noduleType, selectedTablesLabels } = this.state
|
||||
console.log(selectedTablesLabels)
|
||||
|
||||
if (noduleType === 'filter') return <CreateFilterNoduleForm ref={this.filterNoduleForm} />
|
||||
else if (noduleType === 'join') return <CreateJoinNoduleForm ref={this.joinNoduleForm} tables={tablesToImportByLabel || []}/>
|
||||
else if (noduleType === 'transform') return <CreateTransformNoduleForm ref={this.transformNoduleForm} tables={tablesToImportByLabel || []}/>
|
||||
if (noduleType === 'filter') return <CreateFilterNoduleForm ref={this.filterNoduleForm} tables={selectedTablesLabels} />
|
||||
else if (noduleType === 'join') return <CreateJoinNoduleForm ref={this.joinNoduleForm} tables={selectedTablesLabels} />
|
||||
else if (noduleType === 'transform') return <CreateTransformNoduleForm ref={this.transformNoduleForm} tables={selectedTablesLabels} />
|
||||
else return ''
|
||||
}
|
||||
|
||||
@ -99,7 +106,7 @@ class CreateNodule extends Component {
|
||||
fluid
|
||||
/>
|
||||
|
||||
<TableSelect ref={this.tableSelect} />
|
||||
<TableSelect ref={this.tableSelect} onChange={this.handleSelectedTablesChange} />
|
||||
|
||||
<Dropdown
|
||||
value ={this.state.noduleType}
|
||||
@ -111,13 +118,13 @@ class CreateNodule extends Component {
|
||||
]}
|
||||
fluid
|
||||
selection
|
||||
onChange={this.handleChange}
|
||||
onChange={this.handleNoduleTypeChange}
|
||||
/>
|
||||
|
||||
{ this.renderNoduleForm() }
|
||||
|
||||
<div className='creatTableFormSubmitButtons'>
|
||||
<Button content='Cancel' secondary />
|
||||
<Button content='Cancel' secondary onClick={this.clearInput} />
|
||||
<Button content='Confirm' primary onClick={this.handleSubmit} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,34 +1,61 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Input, Grid, Button, Icon, List } from 'semantic-ui-react'
|
||||
import { Input, Grid, Button, Icon, List, Dropdown } from 'semantic-ui-react'
|
||||
import Tables from '../../Models/Tables'
|
||||
import './CreateNodule.css'
|
||||
|
||||
class CreateTransformNoduleForm extends Component {
|
||||
constructor () {
|
||||
super()
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
structure: {}
|
||||
structure: {},
|
||||
tables: props.tables
|
||||
}
|
||||
|
||||
this.tables = new Tables()
|
||||
|
||||
this.initialKeyInput = React.createRef()
|
||||
this.newKeyInput = React.createRef()
|
||||
document.addEventListener('updateTables', this.updateTableList)
|
||||
}
|
||||
|
||||
addStructureInput = () => {
|
||||
let structure = this.state.structure || {}
|
||||
let { structure, keyValue } = this.state
|
||||
|
||||
const initialKey = this.initialKeyInput.current.inputRef.current.value
|
||||
// const initialKey = this.initialKeyInput.current.inputRef.current.value
|
||||
const newKey = this.newKeyInput.current.inputRef.current.value
|
||||
|
||||
if (initialKey && newKey) structure[initialKey] = newKey
|
||||
if (keyValue && newKey) structure[keyValue] = newKey
|
||||
|
||||
this.setState({ structure: structure })
|
||||
}
|
||||
|
||||
componentWillReceiveProps = nextProps => {
|
||||
this.setState({tables: nextProps.tables})
|
||||
}
|
||||
|
||||
getHeadersDropDownOptions = () => {
|
||||
const { tables } = this.state
|
||||
if (!tables || tables.length === 0) return []
|
||||
|
||||
const headers = tables.map(t => {
|
||||
const table = this.tables.getTableByLabel(t)
|
||||
return table.headers
|
||||
}).flat()
|
||||
|
||||
const dropdownOptions = headers.map(h => {
|
||||
return { key: h, text: h, value: h }
|
||||
})
|
||||
return dropdownOptions
|
||||
}
|
||||
|
||||
getStructureProperties = () => {
|
||||
return this.state.structure
|
||||
}
|
||||
|
||||
handleKeyChange = (e, value) => {
|
||||
this.setState({ keyValue: value.value })
|
||||
}
|
||||
|
||||
renderStructure = () => {
|
||||
const { structure } = this.state
|
||||
|
||||
@ -51,7 +78,20 @@ class CreateTransformNoduleForm extends Component {
|
||||
<List celled>
|
||||
{ structureElements.initialKeyElements }
|
||||
</List>
|
||||
<Input placeholder='Initial Key' ref={this.initialKeyInput} fluid />
|
||||
|
||||
|
||||
{/* <Input placeholder='Initial Key' ref={this.initialKeyInput} fluid /> */}
|
||||
<Dropdown
|
||||
clearable
|
||||
search
|
||||
header='Headers'
|
||||
placeholder='Header Key'
|
||||
fluid
|
||||
selection
|
||||
options={this.getHeadersDropDownOptions()}
|
||||
onChange={this.handleKeyChange}
|
||||
/>
|
||||
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<List celled>
|
||||
|
||||
@ -23,6 +23,7 @@ class TableSelect extends Component {
|
||||
|
||||
toggleSelect = (event, element) => {
|
||||
this.setState({ selectedTablesLabels: element.value })
|
||||
this.props.onChange(element.value)
|
||||
}
|
||||
|
||||
updateTableList = () => {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Table } from 'semantic-ui-react'
|
||||
import { Table, Button } from 'semantic-ui-react'
|
||||
import {json2excel} from 'js2excel'
|
||||
import './DataTable.css'
|
||||
|
||||
import Tables from '../../Models/Tables'
|
||||
@ -23,7 +24,8 @@ class DataTable extends Component {
|
||||
if (focusTable) {
|
||||
this.setState({
|
||||
headers: focusTable.headers,
|
||||
tableData: focusTable.rows
|
||||
tableData: focusTable.rows,
|
||||
label: focusTable.label
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -47,9 +49,25 @@ class DataTable extends Component {
|
||||
return tableRowElements
|
||||
}
|
||||
|
||||
saveTable = () => {
|
||||
const { tableData, label } = this.state
|
||||
try {
|
||||
json2excel({
|
||||
data: tableData,
|
||||
name: label
|
||||
})
|
||||
// download(tableFile, this.focusTable.label, 'application/vnd.ms-excel')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
window.alert('Issue downloading Table.')
|
||||
}
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const { tableData, label } = this.state
|
||||
return (
|
||||
<div className='DataTable'>
|
||||
{ tableData.length ? <Button className='saveTableAsExcel' fluid onClick={this.saveTable}>Download { label } Table</Button> : '' }
|
||||
<Table celled>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
|
||||
@ -1,15 +1,38 @@
|
||||
.brandHeader {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 280px;
|
||||
width: 140px;
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.documentationLink{
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
font-size: 18px;
|
||||
padding: 10px 16px;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.documentationLink:hover{
|
||||
color: black;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
.documentationLink img {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.Nav{
|
||||
height: 40px;
|
||||
background-color: #2185d0;
|
||||
color: white;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.appName {
|
||||
@ -21,4 +44,24 @@
|
||||
.logo {
|
||||
width: 36px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
|
||||
.sponsorLink {
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
padding: 1.5px 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.sponsorLink:hover{
|
||||
color: black;
|
||||
background-color: rgb(255, 171, 75);
|
||||
}
|
||||
|
||||
.sponsorLink img {
|
||||
width: 36px;
|
||||
}
|
||||
@ -1,14 +1,19 @@
|
||||
import React, { Component } from 'react'
|
||||
import './Nav.css'
|
||||
import sponsorIcon from '../../img/sponsor.svg'
|
||||
import guideIcon from '../../img/faq.svg'
|
||||
import adaLovelaceLogo from '../../img/adaLovelace.jpg'
|
||||
|
||||
class Nav extends Component {
|
||||
render = () => {
|
||||
return (
|
||||
<div className='Nav'>
|
||||
<a className='documentationLink' target='_blank' rel='noopener noreferrer' href='https://docs.datalovelace.app'><img src={guideIcon} alt='guide button' />Guide</a>
|
||||
<div className='brandHeader'>
|
||||
<span className='appName'>Lovelace</span><img src={adaLovelaceLogo} alt='logo' className='logo' /><span className='appName'>Technology</span>
|
||||
</div>
|
||||
<img src={adaLovelaceLogo} alt='logo' className='logo' />
|
||||
<span className='appName'>Lovelace</span>
|
||||
</div>
|
||||
<a className='sponsorLink' target='_blank' rel='noopener noreferrer' href='https://github.com/sponsors/joshuashoemaker'><img src={sponsorIcon} alt='sponsor button' /> Sponsor</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||