diff --git a/README.md b/README.md index cbd2276..e3f82eb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![dataLab Logo](./assets/graphics/datalablogo.png "dataLab Logo") # dataLab dataLab is a desktop application that enables businesses to stay informed of critical performance metrics through shareable dashboards of live-updating D3 visualizations sourced from their local databases. @@ -10,46 +11,51 @@ The application was made during a 2.5 week project as part of Fullstack Academy' TECHNOLOGIES: React, Redux, Electron, JavaScript, SQL, HTML, CSS, D3, Firebase, Auth0. ## Installation -From your command line: - -```bash -# Clone this repository: +First, clone the repository through git and change to the new directory: +``` git clone https://github.com/dataLabApp/dataLabApp.git -# Navigate to the new directory: cd dataLabApp -# Install the required dependencies: +``` +Then install the required dependencies: +``` npm install -# Run the app: +``` +Run the app: +``` npm start ``` ## Screenshots & Gifs -#### Real-time Updating Dashboards +### Real-time Updating Dashboards ![dataLab Screenshot 1](./assets/gifs/dashboard.gif "dataLab") -#### Beautiful D3 Visualizations +### Beautiful D3 Visualizations ![dataLab Screenshot 2](./assets/gifs/gdp.gif "dataLab") -#### Login via Auth0 -![dataLab Screenshot 3](./assets/gifs/login.gif "dataLab") +### Query and Slice Data from Local Databases +![dataLab Screenshot 3](./assets/gifs/sqlab.gif "dataLab") + +### Create D3 Visualizations from Local Databases +![dataLab Screenshot 4](./assets/gifs/slice.gif "dataLab") + +### Edit D3 Code Directly for Advanced D3 Users +![dataLab Screenshot 5](./assets/gifs/editthecode.gif "dataLab") -#### Export Visualization as SVG -![dataLab Screenshot 4](./assets/gifs/saving.gif "dataLab") +### User Accounts Maintained with Auth0 and Firebase +![dataLab Screenshot 6](./assets/gifs/login.gif "dataLab") -#### Share Visualizations -![dataLab Screenshot 5](./assets/gifs/sharing.gif "dataLab") +### Export D3 Visualization as SVG Object or PDF +![dataLab Screenshot 7](./assets/gifs/saving.gif "dataLab") -#### Query and Slice Data from Local Databases -![dataLab Screenshot 6](./assets/gifs/sqlab.gif "dataLab") +### Share Visualizations +![dataLab Screenshot 8](./assets/gifs/sharing.gif "dataLab") -#### Modify D3 Visualizations -![dataLab Screenshot 7](./assets/gifs/slice.gif "dataLab") ## Contributors -*[Andrew Hookom](https://www.linkedin.com/in/ahookom/) -*[Bruce Grugett](https://www.linkedin.com/in/bruce-grugett/) -*[Mandi Meidlinger](https://www.linkedin.com/in/mandi-meidlinger/) -*[Sara Al Mughairy](https://www.linkedin.com/in/sawra/) +* [Andrew Hookom](https://www.linkedin.com/in/ahookom/) +* [Bruce Grugett](https://www.linkedin.com/in/bruce-grugett/) +* [Mandi Meidlinger](https://www.linkedin.com/in/mandi-meidlinger/) +* [Sara Al Mughairy](https://www.linkedin.com/in/sawra/) ## License MIT © Andrew Hookom, Bruce Grugett, Mandi Meidlinger, Sara Al Mughairy diff --git a/app/components/ExplorerView.jsx b/app/components/ExplorerView.jsx index 4b39a17..3a7d011 100644 --- a/app/components/ExplorerView.jsx +++ b/app/components/ExplorerView.jsx @@ -20,7 +20,7 @@ import {Button} from 'react-bootstrap' class ExplorerView extends Component { constructor(props) { - let lastSlice = props.data.allSlices[props.data.allSlices.length-1] + let lastSlice = props.data.allSlices.length ? props.data.allSlices[props.data.allSlices.length-1] : null super(props) this.state = { showTextEditor: false, @@ -33,21 +33,21 @@ class ExplorerView extends Component { z_label: '', config: { colorScheme: COLOR_SCHEMES[Object.keys(COLOR_SCHEMES)[0]], - sliceId: lastSlice.id, - data: lastSlice.data, + sliceId: lastSlice ? lastSlice.id : null, + data: lastSlice ? lastSlice.data : [], title: 'Click Here to Write Title', dimensions: { fullHeight: 500, fullWidth: 800 }, x: { - dataColumn: Object.keys(lastSlice.data[0])[0] + dataColumn: lastSlice ? Object.keys(lastSlice.data[0])[0] : null }, y: { - dataColumn: Object.keys(lastSlice.data[0])[1] + dataColumn: lastSlice ? Object.keys(lastSlice.data[0])[1] : null }, z: { - dataColumn: Object.keys(lastSlice.data[0])[2] + dataColumn: lastSlice ? Object.keys(lastSlice.data[0])[2] : null } } } diff --git a/app/components/SliceSelector.jsx b/app/components/SliceSelector.jsx index a30d2fc..29bfcc6 100644 --- a/app/components/SliceSelector.jsx +++ b/app/components/SliceSelector.jsx @@ -4,7 +4,7 @@ import {connect} from 'react-redux' class SliceSelector extends Component { render() { - let currentSliceTitle = this.props.data.allSlices.filter(slice => slice.id === this.props.currentSlice)[0].title + let currentSliceTitle = this.props.currentSlice ? this.props.data.allSlices.filter(slice => slice.id === this.props.currentSlice)[0].title : '' return (
diff --git a/app/components/TalkToDatabase.jsx b/app/components/TalkToDatabase.jsx index ae4d913..62ea0f4 100644 --- a/app/components/TalkToDatabase.jsx +++ b/app/components/TalkToDatabase.jsx @@ -37,8 +37,8 @@ class TalkToDatabase extends Component { this.handleSaveSlice = this.handleSaveSlice.bind(this) this.handleSliceNameChange = this.handleSliceNameChange.bind(this) this.handleFindAllDatabases = this.handleFindAllDatabases.bind(this) - this.createRows = this. createRows.bind(this) - this.toggleQueryBox = this. toggleQueryBox.bind(this) + this.createRows = this.createRows.bind(this) + this.toggleQueryBox = this.toggleQueryBox.bind(this) this.handleFindAllDatabases = this.handleFindAllDatabases.bind(this) this.createRows = this.createRows.bind(this) this.changeTab = this.changeTab.bind(this) @@ -220,7 +220,7 @@ class TalkToDatabase extends Component { - +


@@ -269,7 +269,7 @@ class TalkToDatabase extends Component { } - + { (this.state.activeTab =='sliceName' && this.state.currentData) &&
@@ -294,7 +294,7 @@ class TalkToDatabase extends Component { - + ) } } @@ -328,4 +328,4 @@ export default connect(mapStateToProps, mapDispatchToProps)(TalkToDatabase) //
-// \ No newline at end of file +// diff --git a/app/reducers/cardReducer.jsx b/app/reducers/cardReducer.jsx index bf2ba75..e7de900 100644 --- a/app/reducers/cardReducer.jsx +++ b/app/reducers/cardReducer.jsx @@ -31,15 +31,17 @@ export const loadCards = (cards) => ({ }) // ----------- Reducer -const initialState = [{ - id: 1, - title: 'Sample Card', - rawCode: DEFAULT_TEMPLATE, - chart: () => (), - sliceId: 1, - owner: "DuperSet", - public: true -}] +const initialState = [ +// { +// id: 1, +// title: 'Sample Card', +// rawCode: DEFAULT_TEMPLATE, +// chart: () => (), +// sliceId: 1, +// owner: "DuperSet", +// public: true +// } +] initialState.count = 1 diff --git a/app/reducers/dataReducer.jsx b/app/reducers/dataReducer.jsx index 6c0b9f9..1fc5dbc 100644 --- a/app/reducers/dataReducer.jsx +++ b/app/reducers/dataReducer.jsx @@ -47,7 +47,17 @@ export const fetchSliceData = sliceId => { }) } } - +// const seedSliceData = (arrOfSlices) => { +// arrOfSlices.forEach(slice => +// // client.query(`${slice.SQLQuery}`, function(err, data) { +// // if (err)console.error(err) +// // else { +// // slice.data = data.rows +// // } +// // }) +// fetchSliceData(slice.id) +// ) +// } const sampleSliceObj= { id: 1, title: 'Products with Prices', @@ -57,11 +67,51 @@ const sampleSliceObj= { data: [{xVar: 1, yVar: 2, extraVar: 3}] } +const segmentSlice= { + id: 1, + title: 'segmentsById', + dateCreated: new Date(), + SQLQuery: 'SELECT * FROM segments ORDER BY id', + database: 'headSetLaunch', + data: [] +} + +const salesSlice= { + id: 2, + title: 'dailySales', + dateCreated: new Date(), + SQLQuery: 'SELECT * FROM sales ORDER BY id', + database: 'headSetLaunch', + data: [] +} + +const inventorySlice= { + id: 3, + title: 'inventoryByLocation', + dateCreated: new Date(), + SQLQuery: 'SELECT * FROM inventory ORDER BY id', + database: 'headSetLaunch', + data: [] +} + +const tweetSlice= { + id: 4, + title: 'tweetWords', + dateCreated: new Date(), + SQLQuery: 'SELECT * FROM tweets ORDER BY id', + database: 'headSetLaunch', + data: [] +} + +let allSeedSlices = [segmentSlice, salesSlice, inventorySlice, tweetSlice] + +// setTimeout(() => seedSliceData(allSeedSlices), 50) + // ----------- Reducer const initialState = { currentData: [], allSlices: [sampleSliceObj], - sliceIdCounter: 2 + sliceIdCounter: 5 } export default function dataReducer(state = initialState, action) { @@ -85,7 +135,6 @@ export default function dataReducer(state = initialState, action) { } storage.set('data', nextState, function(err) { - if (err) throw err }) return nextState diff --git a/app/utils/areaChart.js b/app/utils/areaChart.js index f8872fb..e938312 100644 --- a/app/utils/areaChart.js +++ b/app/utils/areaChart.js @@ -38,11 +38,11 @@ const fauxNode = window.ReactFauxDOM.createElement('svg') var svg = d3.select(fauxNode) - .attr('width', fullWidth) - .attr('height', fullHeight) + // .attr('width', fullWidth) + // .attr('height', fullHeight) - // .attr('viewBox', `0 0 ${fullWidth} ${fullHeight}`) - // .attr('preserveAspectRatio', 'xMidYMid meet') + .attr('viewBox', `0 0 ${fullWidth} ${fullHeight}`) + .attr('preserveAspectRatio', 'xMidYMid meet') // var svg = d3.select('body').append('svg') // .attr('width', width + margin.left + margin.right) // .attr('height', height + margin.top + margin.bottom) diff --git a/app/utils/barChartFunc.js b/app/utils/barChartFunc.js index 4b44fc6..af0283f 100644 --- a/app/utils/barChartFunc.js +++ b/app/utils/barChartFunc.js @@ -11,9 +11,9 @@ const fullWidth = config.dimensions.fullWidth const fullHeight = config.dimensions.fullHeight const colors = window.d3.scaleOrdinal(config.colorScheme) - const yAxisLabel = 'Dollars' + const yAxisLabel = 'Products Stocked' - const margin = {top: 20, right: 5, bottom: 50, left: 50} + const margin = {top: 20, right: 5, bottom: 50, left: 65} // here, we want the full chart to be 700x200, so we determine // the width and height by subtracting the margins from those values @@ -71,7 +71,7 @@ .style('text-anchor', 'middle') .style('fill', 'black') .attr('dy', '-2.5em') - .style('font-size', 14) + .style('font-size', 20) .text(yAxisLabel) var barHolder = svg.append('g') diff --git a/app/utils/bubbleChart.js b/app/utils/bubbleChart.js index dc2733d..07a3123 100644 --- a/app/utils/bubbleChart.js +++ b/app/utils/bubbleChart.js @@ -46,6 +46,7 @@ node.append('text') .attr('dy', '.3em') + .style('font-size','25') .style('text-anchor', 'middle') .text(function(d) { return d.data.className.substring(0, d.r / 3) }) diff --git a/app/utils/headsetLaunchUtils.js b/app/utils/headsetLaunchUtils.js index 6661a8d..8034534 100644 --- a/app/utils/headsetLaunchUtils.js +++ b/app/utils/headsetLaunchUtils.js @@ -3,13 +3,13 @@ const pg = require('pg') const client = new pg.Client('postgres://localhost/headSetLaunch') client.connect() - +let counter = 1 export function seedHeadsetData() { const continuousSeed = window.setInterval(function() { if (Math.random() < 0.25) seedTweetsOnce() if (Math.random() < 0.25) seedInventoryOnce() if (Math.random() < 0.25) seedSegmentsOnce() - if (Math.random() < 0.25) seedSalesOnce() + if (++counter % 5 === 0) seedSalesOnce() }, 200) window.setTimeout(() => window.clearInterval(continuousSeed), 40*1000) } @@ -105,12 +105,12 @@ function seedInventoryOnce() { const invObj = {'1': 416, '2': 604, '3': 569, '4': 393} function generateReduceInventory() { let amt = 0 - const amount = `${(Math.round(Math.random()*20))}` + const amount = `${(Math.round(Math.random()*5))}` const amountBig = 3 * amount let id = `${Math.floor(Math.random()*4)+ 1}` if (id === '1' || id === '3') amt = amountBig else if (id === '4') amt = amount - else amt = 0 + else amt = amount / 2 const updateAmt = invObj[id] - amt invObj[id] = updateAmt // console.log('amount amt, updateAmt, invObj.id, id', amount, amt, updateAmt, invObj, +id) diff --git a/assets/gifs/editthecode.gif b/assets/gifs/editthecode.gif new file mode 100644 index 0000000..1bad129 Binary files /dev/null and b/assets/gifs/editthecode.gif differ diff --git a/docs/assets/gifs/dashboard.gif b/docs/assets/gifs/dashboard.gif new file mode 100644 index 0000000..e66908a Binary files /dev/null and b/docs/assets/gifs/dashboard.gif differ diff --git a/docs/assets/gifs/editthecode.gif b/docs/assets/gifs/editthecode.gif new file mode 100644 index 0000000..1bad129 Binary files /dev/null and b/docs/assets/gifs/editthecode.gif differ diff --git a/docs/assets/gifs/gdp.gif b/docs/assets/gifs/gdp.gif new file mode 100644 index 0000000..0278036 Binary files /dev/null and b/docs/assets/gifs/gdp.gif differ diff --git a/docs/assets/gifs/login.gif b/docs/assets/gifs/login.gif new file mode 100644 index 0000000..a87fd5a Binary files /dev/null and b/docs/assets/gifs/login.gif differ diff --git a/docs/assets/gifs/saving.gif b/docs/assets/gifs/saving.gif new file mode 100644 index 0000000..81444bf Binary files /dev/null and b/docs/assets/gifs/saving.gif differ diff --git a/docs/assets/gifs/sharing.gif b/docs/assets/gifs/sharing.gif new file mode 100644 index 0000000..8cd31a2 Binary files /dev/null and b/docs/assets/gifs/sharing.gif differ diff --git a/docs/assets/gifs/slice.gif b/docs/assets/gifs/slice.gif new file mode 100644 index 0000000..02e1eae Binary files /dev/null and b/docs/assets/gifs/slice.gif differ diff --git a/docs/assets/gifs/sqlab.gif b/docs/assets/gifs/sqlab.gif new file mode 100644 index 0000000..f1a1a27 Binary files /dev/null and b/docs/assets/gifs/sqlab.gif differ diff --git a/docs/assets/logos/d3-logo.png b/docs/assets/logos/d3-logo.png new file mode 100644 index 0000000..af32116 Binary files /dev/null and b/docs/assets/logos/d3-logo.png differ diff --git a/docs/assets/logos/electron-logo.png b/docs/assets/logos/electron-logo.png new file mode 100644 index 0000000..32d4e2a Binary files /dev/null and b/docs/assets/logos/electron-logo.png differ diff --git a/docs/assets/logos/firebase-logo.png b/docs/assets/logos/firebase-logo.png new file mode 100644 index 0000000..d0d82f5 Binary files /dev/null and b/docs/assets/logos/firebase-logo.png differ diff --git a/docs/assets/logos/html-css-logo.png b/docs/assets/logos/html-css-logo.png new file mode 100644 index 0000000..82e6ce6 Binary files /dev/null and b/docs/assets/logos/html-css-logo.png differ diff --git a/docs/assets/logos/js-logo.png b/docs/assets/logos/js-logo.png new file mode 100644 index 0000000..6e49ebe Binary files /dev/null and b/docs/assets/logos/js-logo.png differ diff --git a/docs/assets/logos/oauth-logo.png b/docs/assets/logos/oauth-logo.png new file mode 100644 index 0000000..1cf8030 Binary files /dev/null and b/docs/assets/logos/oauth-logo.png differ diff --git a/docs/assets/logos/postgres-logo.png b/docs/assets/logos/postgres-logo.png new file mode 100644 index 0000000..6dfc50b Binary files /dev/null and b/docs/assets/logos/postgres-logo.png differ diff --git a/docs/assets/logos/react-logo.png b/docs/assets/logos/react-logo.png new file mode 100644 index 0000000..9ac3e7b Binary files /dev/null and b/docs/assets/logos/react-logo.png differ diff --git a/docs/assets/logos/redux-logo.png b/docs/assets/logos/redux-logo.png new file mode 100644 index 0000000..61ab512 Binary files /dev/null and b/docs/assets/logos/redux-logo.png differ diff --git a/docs/index.html b/docs/index.html index 6122342..d43dd55 100644 --- a/docs/index.html +++ b/docs/index.html @@ -27,7 +27,7 @@ - +
-
- +
-

Beautiful colors

+

Slice Data

- Each color has a strong pigment and was chosen to make your design shine. Each component from our product can have one of these colors. Try on different combinations and be sure that everything works together. + Access information from your company’s local databases. Then filter the data via SQL queries and save slices.

- PSD Custom focuses on conveying the attention of your users to the important parts of the page and the actions. While keeping a light feel, the colors give the page an extra push. + We used pg as the database driver to send the user's SQL commands via postgres to their local databases.


-

Pixel Perfect

+

D3 Visualizations

- Each color has a strong pigment and was chosen to make your design shine. Each component from our product can have one of these colors. Try on different combinations and be sure that everything works together. + After slicing the data, the user is taken to the Explorer page, which is home to the D3 chart creation tools. A control panel allows non-D3 users to easily make charts.

- PSD Custom focuses on conveying the attention of your users to the important parts of the page and the actions. While keeping a light feel, the colors give the page an extra push. + Once a D3 is created, it can be saved to any dashboard.

- + +
+
+
+
+
+
+
+ +
+
+
+

Real-time Dashboards

+

+ Once a D3 chart is created, multiple charts can be combined onto a dashboard. Charts are updated in real-time as the user's local databases are updated. +

+

+ Dashboards let users track the current status of metrics and key performance indicators for their business. +

+
+
+
+
+
+

Sharing

+

+ D3 visualations can be exported as a non-proprietery SVG object or pdf. +

+

+ By using a firebase database, we are able to remotely keep track of all user accounts. So when a user shares a chart with coworkers, they receive a notification on their desktops. +

+
+
+
+
@@ -156,49 +207,75 @@

Pixel Perfect

-

Features

+

Contributors

-
- +
+ Andrew Hookom
-

Client-Perfect Draws

-

All appointments sync with your Google calendar so your availability is always up to date. See your schedule at a glance from any device.

+

Andrew Hookom

+

+ Fullstack Software Engineer +

+

+ Marietta, Georgia +

-
- +
+ Bruce Grugett +
+
+

Bruce Grugett

+

+ Remote Fullstack Software Engineer +

+

+ West Palm Beach, Florida +

-

Retina Ready

-

Automatic text and email reminders make sure customers always remember their upcoming appointments.

-
- +
+ Mandi Meidlinger +
+
+

Mandi Meidlinger

+

+ Remote Fullstack Software Engineer +

+

+ Ithaca, New York +

-

You'll love it

-

You'll Love It, you will find dining room sets, couches, chairs and pre-owned furniture of all kinds as well as lamps, rugs and accessories.

-
- +
+ Sara Al Mughairy +
+
+

Sara Al Mughairy

+

+ Product Designer +

+

+ Seattle, Washington +

-

Big Discount

-

Take payments and run your business on the go, in your store and then see how it all adds up with analytics.

-
--> +