-
Notifications
You must be signed in to change notification settings - Fork 409
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* #84, highlight columns based on dtype * #92, build columns with random values * #111, code export has syntax error & str() fix for column builder names * #116, updated styling of github fork link * #114, added "Export CSV" link * #113, updates to "Value Counts" chart in "Column Analysis" for number of values and ordinal entry * #120, allowing for duplicates in bar charts * #119, fixed bug with queries not being passed to functions * #114, added the ability to export dataframes to CSV/TSV * added "category breakdown" in column analysis popup for float columns * fixed bug where previous "show missing only" selection was not being recognized
- v3.16.0
- v3.15.1
- v3.14.1
- v3.14.0
- v3.13.1
- v3.13.0
- v3.12.0
- v3.11.0
- v3.10.0
- v3.9.0
- v3.8.1
- v3.8.0
- v3.7.0
- v3.6.0
- v3.5.0
- v3.4.0
- v3.3.0
- v3.2.0
- v3.1.7
- v3.1.6
- v3.1.5
- v3.1.4
- v3.1.0
- v3.0.0
- v2.16.0
- v2.15.2
- v2.15.1
- v2.15.0
- v2.14.4
- v2.14.3
- v2.14.2
- v2.14.1
- v2.13.0
- v2.12.3
- v2.12.2
- v2.12.1
- v2.12.0
- v2.11.0
- v2.10.0
- v2.9.1
- v2.8.1
- v2.8.0
- v2.7.1
- v2.6.0
- v2.5.1
- v2.4.0
- v2.3.0
- v2.2.0
- v2.1.2
- v2.1.0
- v2.0.0
- v1.61.0
- v1.60.2
- v1.60.1
- v1.59.1
- v1.58.2
- v1.58.1
- v1.56.0
- v1.55.0
- v1.54.1
- v1.54.0
- v1.53.0
- v1.52.0
- v1.51.0
- v1.49.0
- v1.48.0
- v1.47.0
- v1.46.0
- v1.45.0
- v1.44.1
- v1.44.0
- v1.43.0
- v1.42.1
- v1.42.0
- v1.41.1
- v1.41.0
- v1.40.2
- v1.40.1
- v1.39.0
- v1.38.0
- v1.37.1
- v1.36.0
- v1.35.0
- v1.34.0
- v1.33.1
- v1.33.0
- v.1.32.1
- v1.32.0
- v1.31.0
- v1.30.0
- v1.29.0
- v1.28.1
- v1.28.0
- v1.27.0
- v1.26.0
- v1.25.0
- v1.24.0
- v1.23.0
- v1.22.1
- v1.22.0
- v.1.21.1
- v1.21.0
- v1.20.0
- v1.19.2
- v1.19.1
- v1.19.0
- v1.18.2
- v1.18.1
- v1.18.0
- v1.17.0
- v1.16.0
- v1.15.2
- v1.14.1
- v1.14.0
- v1.13.0
- v1.12.0
- v1.11.0
- v1.10.0
- v1.9.2
- v1.9.1
- v1.9.0
- v1.8.19
- v1.8.17
- v1.8.16
- v1.8.15
- v1.8.14
- v1.8.13
- v1.8.12
- v1.8.11
- v1.8.10
- v1.8.8
- v1.8.7
- v1.8.1
- 2.14.0
- 2.9.0
Andrew Schonfeld
committed
Mar 28, 2020
1 parent
50baa40
commit 0e073eb
Showing
47 changed files
with
2,058 additions
and
573 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,395 @@ | ||
/* eslint max-lines: "off" */ | ||
import { mount } from "enzyme"; | ||
import moment from "moment"; | ||
import React from "react"; | ||
import { Provider } from "react-redux"; | ||
|
||
import { CreateRandom } from "../../../popups/create/CreateRandom"; | ||
import mockPopsicle from "../../MockPopsicle"; | ||
import * as t from "../../jest-assertions"; | ||
import reduxUtils from "../../redux-test-utils"; | ||
import { buildInnerHTML, clickMainMenuButton, withGlobalJquery } from "../../test-utils"; | ||
|
||
const originalOffsetHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight"); | ||
const originalOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetWidth"); | ||
const originalInnerWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerWidth"); | ||
const originalInnerHeight = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerHeight"); | ||
|
||
function initialize(res) { | ||
res | ||
.find("div.form-group") | ||
.first() | ||
.find("input") | ||
.first() | ||
.simulate("change", { target: { value: "rando_col" } }); | ||
res | ||
.find("div.form-group") | ||
.at(1) | ||
.find("button") | ||
.last() | ||
.simulate("click"); | ||
} | ||
|
||
function submit(res) { | ||
res | ||
.find("div.modal-footer") | ||
.first() | ||
.find("button") | ||
.first() | ||
.simulate("click"); | ||
} | ||
|
||
describe("DataViewer tests", () => { | ||
beforeAll(() => { | ||
Object.defineProperty(HTMLElement.prototype, "offsetHeight", { | ||
configurable: true, | ||
value: 500, | ||
}); | ||
Object.defineProperty(HTMLElement.prototype, "offsetWidth", { | ||
configurable: true, | ||
value: 500, | ||
}); | ||
Object.defineProperty(window, "innerWidth", { | ||
configurable: true, | ||
value: 1205, | ||
}); | ||
Object.defineProperty(window, "innerHeight", { | ||
configurable: true, | ||
value: 775, | ||
}); | ||
|
||
const mockBuildLibs = withGlobalJquery(() => | ||
mockPopsicle.mock(url => { | ||
const { urlFetcher } = require("../../redux-test-utils").default; | ||
return urlFetcher(url); | ||
}) | ||
); | ||
|
||
const mockChartUtils = withGlobalJquery(() => (ctx, cfg) => { | ||
const chartCfg = { ctx, cfg, data: cfg.data, destroyed: false }; | ||
chartCfg.destroy = () => (chartCfg.destroyed = true); | ||
chartCfg.getElementsAtXAxis = _evt => [{ _index: 0 }]; | ||
chartCfg.getElementAtEvent = _evt => [{ _datasetIndex: 0, _index: 0, _chart: { config: cfg, data: cfg.data } }]; | ||
return chartCfg; | ||
}); | ||
|
||
jest.mock("popsicle", () => mockBuildLibs); | ||
jest.mock("chart.js", () => mockChartUtils); | ||
jest.mock("chartjs-plugin-zoom", () => ({})); | ||
jest.mock("chartjs-chart-box-and-violin-plot/build/Chart.BoxPlot.js", () => ({})); | ||
}); | ||
|
||
afterAll(() => { | ||
Object.defineProperty(HTMLElement.prototype, "offsetHeight", originalOffsetHeight); | ||
Object.defineProperty(HTMLElement.prototype, "offsetWidth", originalOffsetWidth); | ||
Object.defineProperty(window, "innerWidth", originalInnerWidth); | ||
Object.defineProperty(window, "innerHeight", originalInnerHeight); | ||
}); | ||
|
||
test("DataViewer: build random float column", done => { | ||
const { DataViewer } = require("../../../dtale/DataViewer"); | ||
const CreateColumn = require("../../../popups/create/CreateColumn").ReactCreateColumn; | ||
|
||
const store = reduxUtils.createDtaleStore(); | ||
buildInnerHTML({ settings: "" }, store); | ||
const result = mount( | ||
<Provider store={store}> | ||
<DataViewer /> | ||
</Provider>, | ||
{ attachTo: document.getElementById("content") } | ||
); | ||
|
||
setTimeout(() => { | ||
result.update(); | ||
clickMainMenuButton(result, "Build Column"); | ||
setTimeout(() => { | ||
result.update(); | ||
initialize(result.find(CreateColumn)); | ||
result.update(); | ||
t.equal(result.find(CreateRandom).length, 1, "should show build random column"); | ||
const randomInputs = result.find(CreateRandom).first(); | ||
randomInputs | ||
.find("div.form-group") | ||
.at(1) | ||
.find("input") | ||
.first() | ||
.simulate("change", { target: { value: "-2" } }); | ||
randomInputs | ||
.find("div.form-group") | ||
.last() | ||
.find("input") | ||
.simulate("change", { target: { value: "2" } }); | ||
submit(result); | ||
setTimeout(() => { | ||
t.deepEqual(result.find(CreateColumn).instance().state.cfg, { | ||
type: "float", | ||
low: "-2", | ||
high: "2", | ||
}); | ||
result.update(); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
}, 600); | ||
}); | ||
|
||
test("DataViewer: build random int column", done => { | ||
const { DataViewer } = require("../../../dtale/DataViewer"); | ||
const CreateColumn = require("../../../popups/create/CreateColumn").ReactCreateColumn; | ||
|
||
const store = reduxUtils.createDtaleStore(); | ||
buildInnerHTML({ settings: "" }, store); | ||
const result = mount( | ||
<Provider store={store}> | ||
<DataViewer /> | ||
</Provider>, | ||
{ attachTo: document.getElementById("content") } | ||
); | ||
|
||
setTimeout(() => { | ||
result.update(); | ||
clickMainMenuButton(result, "Build Column"); | ||
setTimeout(() => { | ||
result.update(); | ||
initialize(result.find(CreateColumn)); | ||
result.update(); | ||
const randomInputs = result.find(CreateRandom).first(); | ||
randomInputs | ||
.find("div.form-group") | ||
.first() | ||
.find("button") | ||
.at(1) | ||
.simulate("click"); | ||
randomInputs | ||
.find("div.form-group") | ||
.at(1) | ||
.find("input") | ||
.simulate("change", { target: { value: "-2" } }); | ||
randomInputs | ||
.find("div.form-group") | ||
.last() | ||
.find("input") | ||
.simulate("change", { target: { value: "2" } }); | ||
submit(result); | ||
setTimeout(() => { | ||
t.deepEqual(result.find(CreateColumn).instance().state.cfg, { | ||
type: "int", | ||
low: "-2", | ||
high: "2", | ||
}); | ||
result.update(); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
}, 600); | ||
}); | ||
|
||
test("DataViewer: build random string column", done => { | ||
const { DataViewer } = require("../../../dtale/DataViewer"); | ||
const CreateColumn = require("../../../popups/create/CreateColumn").ReactCreateColumn; | ||
|
||
const store = reduxUtils.createDtaleStore(); | ||
buildInnerHTML({ settings: "" }, store); | ||
const result = mount( | ||
<Provider store={store}> | ||
<DataViewer /> | ||
</Provider>, | ||
{ attachTo: document.getElementById("content") } | ||
); | ||
|
||
setTimeout(() => { | ||
result.update(); | ||
clickMainMenuButton(result, "Build Column"); | ||
setTimeout(() => { | ||
result.update(); | ||
initialize(result.find(CreateColumn)); | ||
result.update(); | ||
const randomInputs = result.find(CreateRandom).first(); | ||
randomInputs | ||
.find("div.form-group") | ||
.first() | ||
.find("button") | ||
.at(2) | ||
.simulate("click"); | ||
randomInputs | ||
.find("div.form-group") | ||
.at(1) | ||
.find("input") | ||
.simulate("change", { target: { value: "5" } }); | ||
randomInputs | ||
.find("div.form-group") | ||
.last() | ||
.find("input") | ||
.simulate("change", { target: { value: "abcde" } }); | ||
submit(result); | ||
setTimeout(() => { | ||
t.deepEqual(result.find(CreateColumn).instance().state.cfg, { | ||
type: "string", | ||
chars: "abcde", | ||
length: "5", | ||
}); | ||
result.update(); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
}, 600); | ||
}); | ||
|
||
test("DataViewer: build random choice column", done => { | ||
const { DataViewer } = require("../../../dtale/DataViewer"); | ||
const CreateColumn = require("../../../popups/create/CreateColumn").ReactCreateColumn; | ||
|
||
const store = reduxUtils.createDtaleStore(); | ||
buildInnerHTML({ settings: "" }, store); | ||
const result = mount( | ||
<Provider store={store}> | ||
<DataViewer /> | ||
</Provider>, | ||
{ attachTo: document.getElementById("content") } | ||
); | ||
|
||
setTimeout(() => { | ||
result.update(); | ||
clickMainMenuButton(result, "Build Column"); | ||
setTimeout(() => { | ||
result.update(); | ||
initialize(result.find(CreateColumn)); | ||
result.update(); | ||
const randomInputs = result.find(CreateRandom).first(); | ||
randomInputs | ||
.find("div.form-group") | ||
.first() | ||
.find("button") | ||
.at(3) | ||
.simulate("click"); | ||
randomInputs | ||
.find("div.form-group") | ||
.at(1) | ||
.find("input") | ||
.simulate("change", { target: { value: "foo,bar,baz" } }); | ||
submit(result); | ||
setTimeout(() => { | ||
t.deepEqual(result.find(CreateColumn).instance().state.cfg, { | ||
type: "choice", | ||
choices: "foo,bar,baz", | ||
}); | ||
result.update(); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
}, 600); | ||
}); | ||
|
||
test("DataViewer: build random bool column", done => { | ||
const { DataViewer } = require("../../../dtale/DataViewer"); | ||
const CreateColumn = require("../../../popups/create/CreateColumn").ReactCreateColumn; | ||
|
||
const store = reduxUtils.createDtaleStore(); | ||
buildInnerHTML({ settings: "" }, store); | ||
const result = mount( | ||
<Provider store={store}> | ||
<DataViewer /> | ||
</Provider>, | ||
{ attachTo: document.getElementById("content") } | ||
); | ||
|
||
setTimeout(() => { | ||
result.update(); | ||
clickMainMenuButton(result, "Build Column"); | ||
setTimeout(() => { | ||
result.update(); | ||
initialize(result.find(CreateColumn)); | ||
result.update(); | ||
const randomInputs = result.find(CreateRandom).first(); | ||
randomInputs | ||
.find("div.form-group") | ||
.first() | ||
.find("button") | ||
.at(4) | ||
.simulate("click"); | ||
submit(result); | ||
setTimeout(() => { | ||
t.deepEqual(result.find(CreateColumn).instance().state.cfg, { | ||
type: "bool", | ||
}); | ||
result.update(); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
}, 600); | ||
}); | ||
|
||
test("DataViewer: build random date column", done => { | ||
const { DataViewer } = require("../../../dtale/DataViewer"); | ||
const CreateColumn = require("../../../popups/create/CreateColumn").ReactCreateColumn; | ||
const DateInput = require("@blueprintjs/datetime").DateInput; | ||
|
||
const store = reduxUtils.createDtaleStore(); | ||
buildInnerHTML({ settings: "" }, store); | ||
const result = mount( | ||
<Provider store={store}> | ||
<DataViewer /> | ||
</Provider>, | ||
{ attachTo: document.getElementById("content") } | ||
); | ||
|
||
setTimeout(() => { | ||
result.update(); | ||
clickMainMenuButton(result, "Build Column"); | ||
setTimeout(() => { | ||
result.update(); | ||
initialize(result.find(CreateColumn)); | ||
result.update(); | ||
const randomInputs = result.find(CreateRandom).first(); | ||
randomInputs | ||
.find("div.form-group") | ||
.first() | ||
.find("button") | ||
.last() | ||
.simulate("click"); | ||
const dateInputs = result.find(CreateColumn).find(DateInput); | ||
dateInputs | ||
.first() | ||
.instance() | ||
.props.onChange(new Date(moment("20000101"))); | ||
dateInputs | ||
.find(DateInput) | ||
.last() | ||
.instance() | ||
.props.onChange(new Date(moment("20000102"))); | ||
result | ||
.find(CreateColumn) | ||
.find("i") | ||
.first() | ||
.simulate("click"); | ||
result | ||
.find(CreateColumn) | ||
.find("i") | ||
.last() | ||
.simulate("click"); | ||
submit(result); | ||
setTimeout(() => { | ||
t.deepEqual(result.find(CreateColumn).instance().state.cfg, { | ||
type: "date", | ||
start: "20000101", | ||
end: "20000102", | ||
businessDay: true, | ||
timestamps: true, | ||
}); | ||
result.update(); | ||
done(); | ||
}, 400); | ||
}, 400); | ||
}, 600); | ||
}); | ||
|
||
test("DataViewer: build random cfg validation", done => { | ||
const { validateRandomCfg } = require("../../../popups/create/CreateRandom"); | ||
t.equal( | ||
validateRandomCfg({ type: "int", low: "3", high: "2" }), | ||
"Invalid range specification, low must be less than high!" | ||
); | ||
t.equal(validateRandomCfg({ type: "date", start: "20000101", end: "19991231" }), "Start must be before End!"); | ||
done(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +0,0 @@ | ||
.btn-group.column-sorting .btn.btn-primary { | ||
border: solid 1px #a7b3b7; | ||
-webkit-box-shadow: 0 1px 1px 0 rgba(112, 130, 136, 0.2); | ||
box-shadow: 0 1px 1px 0 rgba(112, 130, 136, 0.2); | ||
background: -webkit-gradient(linear, left top, left bottom, color-stop(80%, white), to(#ebedee)); | ||
background: linear-gradient(to bottom, white 80%, #ebedee); | ||
color: #404040; | ||
} | ||
|
||
.btn-group.column-sorting .btn.btn-primary:enabled:hover, | ||
.btn-group.column-sorting .btn.btn-primary:enabled:focus { | ||
border: solid 1px #99a7ac; | ||
-webkit-box-shadow: 0 1px 1px 0 rgba(112, 130, 136, 0.3); | ||
box-shadow: 0 1px 1px 0 rgba(112, 130, 136, 0.3); | ||
} | ||
|
||
.btn-group.column-sorting .btn.btn-primary:enabled:active { | ||
border: solid 1px #99a7ac; | ||
-webkit-box-shadow: 0 0 1px 0 rgba(112, 130, 136, 0.3), 0 0 1px 0 #cfd4d7 inset; | ||
box-shadow: 0 0 1px 0 rgba(112, 130, 136, 0.3), 0 0 1px 0 #cfd4d7 inset; | ||
background: -webkit-gradient(linear, left top, left bottom, from(#ebedee), to(white)); | ||
background: linear-gradient(to bottom, #ebedee, white); | ||
} | ||
|
||
.btn-group.column-sorting .btn.btn-primary.active { | ||
border: solid 1px #88989e; | ||
-webkit-box-shadow: 0 0 3px 0 #88989e inset; | ||
box-shadow: 0 0 3px 0 #88989e inset; | ||
background: #a7b3b7; | ||
color: white; | ||
} | ||
|
||
.btn-group.column-sorting .btn.btn-primary.active:enabled:hover { | ||
border: solid 1px #88989e; | ||
-webkit-box-shadow: 0 0 3px 0 #88989e inset; | ||
box-shadow: 0 0 3px 0 #88989e inset; | ||
background: #a7b3b7; | ||
cursor: default; | ||
} | ||
|
||
.btn-group.column-sorting .btn.btn-primary.active:enabled:active, | ||
.btn-group.column-sorting .btn.btn-primary.active:enabled:focus { | ||
border: solid 1px #88989e; | ||
-webkit-box-shadow: 0 0 3px 0 #88989e inset; | ||
box-shadow: 0 0 3px 0 #88989e inset; | ||
background: #a7b3b7; | ||
} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
input.column-analysis-filter { | ||
padding: 0.45rem; | ||
} | ||
|
||
.ordinal-dd .Select { | ||
min-width: 10em; | ||
} | ||
|
||
.ordinal-dd .is-clearable.Select--single .Select__value-container--has-value > .Select__single-value { | ||
padding-right: 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import qs from "querystring"; | ||
|
||
import _ from "lodash"; | ||
import PropTypes from "prop-types"; | ||
import React from "react"; | ||
import { connect } from "react-redux"; | ||
|
||
import { RemovableError } from "../../RemovableError"; | ||
import actions from "../../actions/dtale"; | ||
import { buildURLParams } from "../../actions/url-utils"; | ||
import chartUtils from "../../chartUtils"; | ||
import { fetchJson } from "../../fetcher"; | ||
import { ColumnAnalysisFilters } from "./ColumnAnalysisFilters"; | ||
import { createChart } from "./columnAnalysisUtils"; | ||
|
||
require("./ColumnAnalysis.css"); | ||
|
||
const BASE_ANALYSIS_URL = "/dtale/column-analysis"; | ||
|
||
class ReactColumnAnalysis extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { chart: null, type: null, error: null, chartParams: null }; | ||
this.buildAnalysis = this.buildAnalysis.bind(this); | ||
} | ||
|
||
shouldComponentUpdate(newProps, newState) { | ||
if (!_.isEqual(this.props, newProps)) { | ||
return true; | ||
} | ||
const updateState = ["type", "error", "chartParams"]; | ||
if (!_.isEqual(_.pick(this.state, updateState), _.pick(newState, updateState))) { | ||
return true; | ||
} | ||
|
||
if (this.state.chart != newState.chart) { | ||
// Don't re-render if we've only changed the chart. | ||
return false; | ||
} | ||
|
||
// Otherwise, use the default react behavior. | ||
return false; | ||
} | ||
|
||
componentDidMount() { | ||
this.buildAnalysis(); | ||
} | ||
|
||
buildAnalysis(chartParams) { | ||
const finalParams = chartParams || this.state.chartParams; | ||
const { selectedCol } = this.props.chartData; | ||
const paramProps = ["selectedCol", "bins", "top", "type", "ordinalCol", "ordinalAgg", "categoryCol", "categoryAgg"]; | ||
const params = _.assignIn({}, this.props.chartData, _.pick(finalParams, ["bins", "top"])); | ||
params.type = _.get(finalParams, "type"); | ||
if (params.type === "categories" && _.isNull(finalParams.categoryCol)) { | ||
return; | ||
} | ||
const subProps = params.type === "value_counts" ? ["ordinalCol", "ordinalAgg"] : ["categoryCol", "categoryAgg"]; | ||
_.forEach(subProps, p => (params[p] = _.get(finalParams, [p, "value"]))); | ||
const url = `${BASE_ANALYSIS_URL}/${this.props.dataId}?${qs.stringify(buildURLParams(params, paramProps))}`; | ||
fetchJson(url, fetchedChartData => { | ||
const newState = { error: null, chartParams: finalParams }; | ||
if (_.get(fetchedChartData, "error")) { | ||
newState.error = <RemovableError {...fetchedChartData} />; | ||
} | ||
newState.code = _.get(fetchedChartData, "code", ""); | ||
newState.dtype = _.get(fetchedChartData, "dtype", ""); | ||
newState.type = _.get(fetchedChartData, "chart_type", "histogram"); | ||
newState.query = _.get(fetchedChartData, "query"); | ||
newState.cols = _.get(fetchedChartData, "cols", []); | ||
const builder = ctx => { | ||
if (!_.get(fetchedChartData, "data", []).length) { | ||
return null; | ||
} | ||
return createChart(ctx, fetchedChartData, _.assignIn(finalParams, { selectedCol, type: newState.type })); | ||
}; | ||
newState.chart = chartUtils.chartWrapper("columnAnalysisChart", this.state.chart, builder); | ||
this.setState(newState); | ||
}); | ||
} | ||
|
||
render() { | ||
let description = null; | ||
if (actions.isPopup()) { | ||
description = ( | ||
<div key="description" className="modal-header"> | ||
<h4 className="modal-title"> | ||
<i className="ico-equalizer" /> | ||
{` ${this.state.type === "histogram" ? "Histogram" : "Value Counts"} for `} | ||
<strong>{_.get(this.props, "chartData.selectedCol")}</strong> | ||
{this.state.query && <small>{this.state.query}</small>} | ||
<div id="describe" /> | ||
</h4> | ||
</div> | ||
); | ||
} | ||
let filters = null; | ||
if (this.state.type) { | ||
filters = ( | ||
<div key="inputs" className="modal-body modal-form"> | ||
<ColumnAnalysisFilters | ||
{..._.pick(this.state, ["type", "cols", "dtype", "code"])} | ||
chartType={this.state.type} | ||
buildChart={this.buildAnalysis} | ||
/> | ||
</div> | ||
); | ||
} | ||
return [ | ||
description, | ||
filters, | ||
<div key="body" className="modal-body"> | ||
{this.state.error || null} | ||
<canvas id="columnAnalysisChart" height={this.props.height} /> | ||
</div>, | ||
]; | ||
} | ||
} | ||
ReactColumnAnalysis.displayName = "ColumnAnalysis"; | ||
ReactColumnAnalysis.propTypes = { | ||
dataId: PropTypes.string.isRequired, | ||
chartData: PropTypes.shape({ | ||
visible: PropTypes.bool.isRequired, | ||
selectedCol: PropTypes.string, | ||
query: PropTypes.string, | ||
}), | ||
height: PropTypes.number, | ||
}; | ||
ReactColumnAnalysis.defaultProps = { height: 400 }; | ||
|
||
const ReduxColumnAnalysis = connect(state => _.pick(state, ["dataId", "chartData"]))(ReactColumnAnalysis); | ||
|
||
export { ReactColumnAnalysis, ReduxColumnAnalysis as ColumnAnalysis }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
import _ from "lodash"; | ||
import PropTypes from "prop-types"; | ||
import React from "react"; | ||
import Select, { createFilter } from "react-select"; | ||
|
||
import { findColType } from "../../dtale/gridUtils"; | ||
import { renderCodePopupAnchor } from "../CodePopup"; | ||
import { AGGREGATION_OPTS } from "../charts/Aggregations"; | ||
|
||
function createSelect(selectProps, labelProp = "value") { | ||
return ( | ||
<Select | ||
className="Select is-clearable is-searchable Select--single" | ||
classNamePrefix="Select" | ||
getOptionLabel={_.property(labelProp)} | ||
getOptionValue={_.property("value")} | ||
filterOption={createFilter({ ignoreAccents: false })} | ||
{...selectProps} | ||
/> | ||
); | ||
} | ||
class ColumnAnalysisFilters extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
type: props.type, | ||
bins: "20", | ||
top: "100", | ||
ordinalCol: null, | ||
categoryCol: null, | ||
}; | ||
this.state.ordinalAgg = _.find(AGGREGATION_OPTS, { value: "sum" }); | ||
this.state.categoryAgg = _.find(AGGREGATION_OPTS, { value: "mean" }); | ||
this.buildChart = this.buildChart.bind(this); | ||
this.buildChartTypeToggle = this.buildChartTypeToggle.bind(this); | ||
this.buildFilter = this.buildFilter.bind(this); | ||
this.buildOrdinalInputs = this.buildOrdinalInputs.bind(this); | ||
this.buildCategoryInputs = this.buildCategoryInputs.bind(this); | ||
} | ||
|
||
shouldComponentUpdate(newProps, newState) { | ||
const props = ["cols", "dtype", "code"]; | ||
if (!_.isEqual(_.pick(this.props, props), _.pick(newProps, props))) { | ||
return true; | ||
} | ||
return !_.isEqual(this.state, newState); | ||
} | ||
|
||
buildChartTypeToggle() { | ||
const colType = findColType(this.props.dtype); | ||
const options = [["Histogram", "histogram"]]; | ||
if (colType === "float") { | ||
options.push(["Categories", "categories"]); | ||
} else { | ||
options.push(["Value Counts", "value_counts"]); | ||
} | ||
return ( | ||
<div className="col-auto btn-group"> | ||
{_.map(options, ([label, value]) => { | ||
const buttonProps = { className: "btn" }; | ||
if (value === this.state.type) { | ||
buttonProps.className += " btn-primary active"; | ||
} else { | ||
buttonProps.className += " btn-primary inactive"; | ||
buttonProps.onClick = () => this.setState({ type: value }, this.buildChart); | ||
} | ||
return ( | ||
<button key={value} {...buttonProps}> | ||
{label} | ||
</button> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
|
||
buildFilter(prop) { | ||
const colType = findColType(this.props.dtype); | ||
const updateFilter = e => { | ||
if (e.key === "Enter") { | ||
if (this.state[prop] && parseInt(this.state[prop])) { | ||
this.buildChart(); | ||
} | ||
e.preventDefault(); | ||
} | ||
}; | ||
return [ | ||
<div key={0} className={`col-auto text-center pr-4 ${colType === "int" ? "pl-0" : ""}`}> | ||
<div> | ||
<b>{_.capitalize(prop)}</b> | ||
</div> | ||
<div style={{ marginTop: "-.5em" }}> | ||
<small>(Please edit)</small> | ||
</div> | ||
</div>, | ||
<div key={1} style={{ width: "3em" }} data-tip="Press ENTER to submit" className="mb-auto mt-auto"> | ||
<input | ||
type="text" | ||
className="form-control text-center column-analysis-filter" | ||
value={this.state[prop]} | ||
onChange={e => this.setState({ [prop]: e.target.value })} | ||
onKeyPress={updateFilter} | ||
/> | ||
</div>, | ||
]; | ||
} | ||
|
||
buildChart() { | ||
this.props.buildChart(this.state); | ||
} | ||
|
||
buildOrdinalInputs() { | ||
const updateOrdinal = (prop, val) => { | ||
const currState = _.assignIn({}, _.pick(this.state, ["ordinalCol", "ordinalAgg"]), { [prop]: val }); | ||
this.setState(currState, () => { | ||
if (currState.ordinalCol && currState.ordinalAgg) { | ||
this.buildChart(); | ||
} | ||
}); | ||
}; | ||
const { cols } = this.props; | ||
let colOpts = _.filter(cols, c => _.includes(["float", "int"], findColType(c.dtype))); | ||
colOpts = _.sortBy( | ||
_.map(colOpts, c => ({ value: c.name })), | ||
c => _.toLower(c.value) | ||
); | ||
return [ | ||
<div key={0} className="col-auto text-center pr-4"> | ||
<div> | ||
<b>Ordinal</b> | ||
</div> | ||
<div style={{ marginTop: "-.5em" }}> | ||
<small>(Choose Col/Agg)</small> | ||
</div> | ||
</div>, | ||
<div key={1} className="col-auto pl-0 mr-3 ordinal-dd"> | ||
{createSelect({ | ||
value: this.state.ordinalCol, | ||
options: colOpts, | ||
onChange: v => updateOrdinal("ordinalCol", v), | ||
noOptionsText: () => "No columns found", | ||
isClearable: true, | ||
})} | ||
</div>, | ||
<div key={2} className="col-auto pl-0 mr-3 ordinal-dd"> | ||
{createSelect( | ||
{ | ||
value: this.state.ordinalAgg, | ||
options: AGGREGATION_OPTS, | ||
onChange: v => updateOrdinal("ordinalAgg", v), | ||
}, | ||
"label" | ||
)} | ||
</div>, | ||
]; | ||
} | ||
|
||
buildCategoryInputs() { | ||
const updateCategory = (prop, val) => { | ||
const currState = _.assignIn({}, _.pick(this.state, ["categoryCol", "categoryAgg"]), { [prop]: val }); | ||
this.setState(currState, () => { | ||
if (currState.categoryCol && currState.categoryAgg) { | ||
this.buildChart(); | ||
} | ||
}); | ||
}; | ||
const { cols } = this.props; | ||
let colOpts = _.reject(cols, c => findColType(c.dtype) === "float"); | ||
colOpts = _.sortBy( | ||
_.map(colOpts, c => ({ value: c.name })), | ||
c => _.toLower(c.value) | ||
); | ||
return [ | ||
<div key={0} className="col-auto text-center pr-4"> | ||
<div> | ||
<b>Category Breakdown</b> | ||
</div> | ||
<div style={{ marginTop: "-.5em" }}> | ||
<small>(Choose Col/Agg)</small> | ||
</div> | ||
</div>, | ||
<div key={1} className="col-auto pl-0 mr-3 ordinal-dd"> | ||
{createSelect({ | ||
value: this.state.categoryCol, | ||
options: colOpts, | ||
onChange: v => updateCategory("categoryCol", v), | ||
noOptionsText: () => "No columns found", | ||
isClearable: true, | ||
})} | ||
</div>, | ||
<div key={2} className="col-auto pl-0 mr-3 ordinal-dd"> | ||
{createSelect( | ||
{ | ||
value: this.state.categoryAgg, | ||
options: AGGREGATION_OPTS, | ||
onChange: v => updateCategory("categoryAgg", v), | ||
}, | ||
"label" | ||
)} | ||
</div>, | ||
]; | ||
} | ||
|
||
render() { | ||
if (_.isNull(this.props.type)) { | ||
return null; | ||
} | ||
const { code, dtype } = this.props; | ||
const colType = findColType(dtype); | ||
const title = this.state.type === "histogram" ? "Histogram" : "Value Counts"; | ||
let filterMarkup = null; | ||
if ("int" === colType) { | ||
// int -> Value Counts or Histogram | ||
if (this.state.type === "histogram") { | ||
filterMarkup = ( | ||
<div className="col row"> | ||
{this.buildChartTypeToggle()} | ||
{this.buildFilter("bins")} | ||
</div> | ||
); | ||
} else { | ||
filterMarkup = ( | ||
<div className="col row"> | ||
{this.buildChartTypeToggle()} | ||
{this.buildFilter("top")} | ||
{this.buildOrdinalInputs()} | ||
</div> | ||
); | ||
} | ||
} else if ("float" === colType) { | ||
// floats -> Histogram or Categories | ||
if (this.state.type === "histogram") { | ||
filterMarkup = ( | ||
<div className="col row"> | ||
{this.buildChartTypeToggle()} | ||
{this.buildFilter("bins")} | ||
</div> | ||
); | ||
} else { | ||
filterMarkup = ( | ||
<div className="col row"> | ||
{this.buildChartTypeToggle()} | ||
{this.buildFilter("top")} | ||
{this.buildCategoryInputs()} | ||
</div> | ||
); | ||
} | ||
} else { | ||
// date, string, bool -> Value Counts | ||
filterMarkup = ( | ||
<div className="col row"> | ||
<h4 className="pl-5 pt-3 modal-title font-weight-bold">{title}</h4> | ||
{this.buildFilter("top")} | ||
{this.buildOrdinalInputs()} | ||
</div> | ||
); | ||
} | ||
return ( | ||
<div className="form-group row small-gutters mb-0"> | ||
{filterMarkup} | ||
<div className="col-auto"> | ||
<div>{renderCodePopupAnchor(code, title)}</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} | ||
ColumnAnalysisFilters.displayName = "ColumnAnalysisFilters"; | ||
ColumnAnalysisFilters.propTypes = { | ||
selectedCol: PropTypes.string, | ||
cols: PropTypes.array, | ||
dtype: PropTypes.string, | ||
code: PropTypes.string, | ||
type: PropTypes.string, | ||
buildChart: PropTypes.func, | ||
}; | ||
|
||
export { ColumnAnalysisFilters }; |
Oops, something went wrong.