├── CODE_OF_CONDUCT.md
├── bower.json
├── src
├── filters
│ ├── BaseFilter.js
│ ├── DownloadButton.js
│ ├── SliderInput.js
│ ├── ApiButton.js
│ ├── SelectButton.js
│ ├── CheckboxGroup.js
│ ├── factory.js
│ ├── ConditionalSelectButton.js
│ └── DynamicSearchInput.js
├── charts
│ ├── plotly
│ │ └── base.js
│ ├── nvd3
│ │ ├── pieChart.js
│ │ ├── base.js
│ │ └── twoAxisFocus.js
│ ├── datamaps
│ │ └── base.js
│ ├── factory.js
│ ├── metricsgraphics
│ │ └── base.js
│ └── datatables
│ │ └── base.js
├── index.js
└── layouts
│ └── FilterChart.js
├── README.md
├── LICENSE.txt
├── package.json
└── webpack.config.js
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | We are committed to keeping our community open and inclusive.
4 |
5 | **Our Code of Conduct can be found here**:
6 | http://opensource.stitchfix.com/code-of-conduct.html
7 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pyxley",
3 | "version": "0.0.6",
4 | "homepage": "https://github.com/stitchfix/pyxleyJS",
5 | "description": "React components for use with pyxley python package",
6 | "main": "build/pyxley.js",
7 | "keywords": [
8 | "react",
9 | "dashboards"
10 | ],
11 | "authors": [
12 | "Nicholas Kridler"
13 | ],
14 | "license": "MIT",
15 | "ignore": [
16 | "node_modules",
17 | "bower_components",
18 | "test",
19 | "tests"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/filters/BaseFilter.js:
--------------------------------------------------------------------------------
1 | import 'bootstrap/dist/css/bootstrap.min.css';
2 | import React from 'react';
3 |
4 | export default class BaseFilter extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | value: null,
9 | selected: 0
10 | };
11 | }
12 |
13 | getCurrentState() {
14 | var result = {};
15 | result[this.props.options.alias] = this.state.value ||
16 | this.props.options.default;
17 | return result;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/filters/DownloadButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Button} from 'react-bootstrap';
3 | import BaseFilter from './BaseFilter';
4 |
5 | export class DownloadButton extends BaseFilter {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | _onClick() {
11 | var params = this.props.onChange();
12 | var url = this.props.options.url.concat("?", $.param(params));
13 | window.location.href = url;
14 | }
15 |
16 | render() {
17 | return (
18 | {this.props.options.label}
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/charts/plotly/base.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | export class PlotlyAPI extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | _update(params) {
10 | var url = this.props.options.url.concat("?",
11 | $.param(params));
12 | $.get(url,
13 | function(result) {
14 | Plotly.newPlot(
15 | this.props.options.chartid,
16 | result.data,
17 | result.layout,
18 | result.config
19 | )
20 | }.bind(this)
21 | );
22 | }
23 |
24 | componentDidMount() {
25 | this._update(this.props.options.params);
26 |
27 | }
28 |
29 | render() {
30 | return (
31 |
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export {ChartFactory} from './charts/factory.js';
4 | export {FilterFactory} from './filters/factory.js';
5 | export {FilterChart} from './layouts/FilterChart';
6 | export {Filter} from './filters/factory.js';
7 | export {Chart} from './charts/factory.js';
8 | export {Table} from './charts/datatables/base.js';
9 | export {MetricsGraphics} from './charts/metricsgraphics/base.js';
10 | export {NVD3Chart} from './charts/nvd3/base.js';
11 | export {Datamaps} from './charts/nvd3/base.js';
12 | export {PlotlyAPI} from './charts/plotly/base.js';
13 | import {SelectButton} from './filters/SelectButton';
14 | import {ConditionalSelectButton} from './filters/ConditionalSelectButton';
15 | import {ApiButton} from './filters/ApiButton';
16 | import {DownloadButton} from './filters/DownloadButton';
17 | import {SliderInput} from './filters/SliderInput';
18 | import {DynamicSearchInput} from './filters/DynamicSearchInput';
19 | import {CheckboxGroup} from './filters/CheckboxGroup';
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pyxley
2 |
3 | `pyxley.js` is a complement to the [pxyley](http://github.com/stitchfix/pyxley) python package. It provides a set of simple React components commonly used in developing lightweight dashboards and web applications.
4 |
5 | ## Charts & Filters
6 | The core idea of `pyxley` is that most dashboards are a simple collection of charts and filters. React provides a common interface that simplifies the integration with Python. Specifics for each chart and filter component are supplied as props in a single master component.
7 |
8 | ## Wrappers
9 | `pyxley` is predominantly a pattern. This allows us to easily integrate with existing Javascript packages. Utilizing React, `pyxley` provides a number of simple wrappers to packages such as [MetricsGraphics.js](http://metricsgraphicsjs.org/) and [React-Bootstrap](http://react-bootstrap.github.io/).
10 |
11 | ## Installation
12 | Install `pyxley` using npm
13 | ```
14 | npm install pyxley
15 | ```
16 | or install using Bower
17 | ```
18 | bower install pyxley
19 | ```
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Stitch Fix
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the Software), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/src/filters/SliderInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import BaseFilter from './BaseFilter';
4 |
5 | export class SliderInput extends BaseFilter {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | _handleChange(e) {
11 | e.preventDefault();
12 | var slider = ReactDOM.findDOMNode(this);
13 | this.setState({value: slider.value});
14 |
15 | if(this.props.dynamic){
16 | var result = {
17 | alias: this.props.options.alias,
18 | value: slider.value
19 | };
20 | this.props.onChange([result]);
21 |
22 | }
23 | }
24 |
25 | render() {
26 |
27 | return (
28 |
37 | );
38 |
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/charts/nvd3/pieChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import nv from 'nvd3';
3 | import d3 from 'd3';
4 |
5 | var PieChart = function() {
6 | this.chart = nv.models.pieChart();
7 | }
8 |
9 | PieChart.prototype.initialize = function(options){
10 | this.chart
11 | .x(function(d) { return d.label})
12 | .y(function(d) { return d.value })
13 | .showLabels(true)
14 | .labelType(options.labelType)
15 | .showLegend(true)
16 | .labelsOutside(false)
17 | .color(options.colors);
18 |
19 | this.chart.tooltip.contentGenerator(
20 | function(x){
21 | return ''+x.data.label+" "+x.data.value*100+"%
"
22 | });
23 | };
24 |
25 | PieChart.prototype.get = function(chartid, url, params){
26 | d3.json(url.concat("?", $.param(params)),
27 | function(error, result) {
28 | var svg = d3.select("#".concat(chartid, " svg"))
29 | .datum(result.data)
30 | .transition()
31 | .duration(500)
32 | .call(this.chart);
33 | nv.utils.windowResize(this.chart.update);
34 |
35 | }.bind(this));
36 | };
37 |
38 | export default PieChart;
39 |
--------------------------------------------------------------------------------
/src/charts/datamaps/base.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export class Datamaps extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {
7 | chart: null
8 | };
9 | }
10 |
11 | componentDidMount() {
12 | this._initialize(this.props.options.params);
13 | }
14 |
15 | _initialize(params) {
16 | var url = this.props.options.url.concat("?", $.param(params));
17 | $.get(url,
18 | function(result){
19 | this.state.chart = new Datamap({
20 | element: document.getElementById(this.props.options.chartid),
21 | scope: 'usa',
22 | fills: result.fills,
23 | data: result.data
24 | });
25 | }.bind(this))
26 | }
27 |
28 | _update(params){
29 | var url = this.props.options.url.concat("?", $.param(params))
30 | $.get(url,
31 | function(result){
32 | this.state.chart.updateChoropleth(result.data);
33 | }.bind(this));
34 | }
35 |
36 | render() {
37 | return (
38 |
41 |
42 | );
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/charts/factory.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {MetricsGraphics} from './metricsgraphics/base.js';
3 | import {NVD3Chart} from './nvd3/base.js';
4 | import {Table} from './datatables/base.js';
5 | import {Datamaps} from './datamaps/base.js';
6 | import {PlotlyAPI} from './plotly/base.js';
7 |
8 | var ChartFactory = function(type) {
9 |
10 | if (typeof ChartFactory[type] != 'function'){
11 | throw new Error(type + ' is not a valid chart type.');
12 | }
13 |
14 | return ChartFactory[type];
15 | };
16 |
17 |
18 | ChartFactory.MetricsGraphics = MetricsGraphics;
19 | ChartFactory.Table = Table;
20 | ChartFactory.NVD3Chart = NVD3Chart;
21 | ChartFactory.Datamaps = Datamaps;
22 | ChartFactory.PlotlyAPI = PlotlyAPI;
23 |
24 | class Chart extends React.Component {
25 | constructor(props) {
26 | super(props);
27 | }
28 |
29 | update(params) {
30 | this.refs.chart._update(params);
31 | }
32 |
33 | render() {
34 | var Z = this.props.factory(this.props.type);
35 | return (
36 |
37 | );
38 | }
39 | }
40 | Chart.defaultProps = {
41 | type: React.PropTypes.string,
42 | factory: ChartFactory,
43 | options: React.PropTypes.object
44 | };
45 |
46 | export {Chart};
47 | export {ChartFactory};
48 |
--------------------------------------------------------------------------------
/src/filters/ApiButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseFilter from './BaseFilter';
3 | import {SelectButton} from './SelectButton';
4 |
5 | export class ApiButton extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | items: []
10 | };
11 | }
12 |
13 | getCurrentState() {
14 | return this.refs.apiBtn.getCurrentState();
15 | }
16 |
17 | componentDidMount() {
18 | this._fetchAPIData({});
19 | }
20 |
21 | _fetchAPIData(params) {
22 | $.get(this.props.options.url.concat('?', $.param(params)),
23 | function(result){
24 | this.setState({items: result.data});
25 | }.bind(this)
26 | );
27 | }
28 |
29 | update(params) {
30 | this._fetchAPIData(params);
31 | }
32 |
33 | render() {
34 | var opts = {
35 | items: this.state.items,
36 | alias: this.props.options.alias,
37 | default: this.props.options.default,
38 | label: this.props.options.label
39 | };
40 | return (
41 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/filters/SelectButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseFilter from './BaseFilter';
3 | import {DropdownButton, MenuItem} from 'react-bootstrap';
4 |
5 | export class SelectButton extends BaseFilter {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | _handleClick(index, text, e) {
11 | e.preventDefault();
12 | this.setState({
13 | selected: index,
14 | value: text
15 | });
16 |
17 | if(this.props.dynamic){
18 | var result = {
19 | alias: this.props.options.alias,
20 | value: text
21 | };
22 | this.props.onChange([result]);
23 | }
24 | }
25 |
26 | render() {
27 | var items = this.props.options.items.map(function(item, index){
28 | return (
29 |
32 | {item}
33 |
34 | );
35 |
36 | }.bind(this));
37 | var label = this.state.value || this.props.options.label;
38 | return (
39 |
43 | {items}
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/filters/CheckboxGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Input, Button} from 'react-bootstrap';
3 |
4 | export class CheckboxGroup extends React.Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | }
9 |
10 | getCurrentState() {
11 | var result = {};
12 | for(var i = 0; i < this.props.options.labels.length; i++){
13 | result[this.props.options.aliases[i]] =
14 | this.refs["checkbox_".concat(i)].getChecked();
15 | }
16 | return result;
17 | }
18 |
19 | _resetChecked() {
20 | for(var i = 0; i < this.props.options.labels.length; i++){
21 | this.refs["checkbox_".concat(i)].getInputDOMNode().checked = false;
22 | }
23 | }
24 |
25 | render() {
26 | var labels = this.props.options.labels.map(function(item, index){
27 | return (
28 |
31 | );
32 | });
33 | return (
34 |
35 |
36 | Submit
37 | Reset
38 |
39 | {labels}
40 |
41 | );
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pyxley",
3 | "version": "0.0.6",
4 | "description": "react components for dashboards",
5 | "main": "build/pyxley.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/stitchfix/pyxleyJS.git"
12 | },
13 | "keywords": [
14 | "react",
15 | "dashboard"
16 | ],
17 | "author": "Nicholas Kridler",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/stitchfix/pyxleyJS/issues"
21 | },
22 | "homepage": "https://github.com/stitchfix/pyxleyJS",
23 | "devDependencies": {
24 | "babel-cli": "^6.5.1",
25 | "babel-core": "^6.5.0",
26 | "babel-loader": "^6.2.2",
27 | "babel-preset-es2015": "^6.5.0",
28 | "babel-preset-react": "^6.5.0",
29 | "bootstrap": "^3.3.6",
30 | "classnames": "^2.1.2",
31 | "css-loader": "^0.23.1",
32 | "datamaps": "^0.4.4",
33 | "datatables": "^1.10.7",
34 | "file-loader": "^0.8.5",
35 | "image-loader": "0.0.1",
36 | "metrics-graphics": "^2.8.0",
37 | "node-libs-browser": "^0.5.2",
38 | "node-sass": "^3.4.2",
39 | "nvd3": "^1.8.2",
40 | "plotly.js": "^1.5.2",
41 | "react": "^0.14.7",
42 | "react-bootstrap": "^0.28.2",
43 | "react-dom": "^0.14.7",
44 | "sass-loader": "^3.1.2",
45 | "style-loader": "^0.13.0",
46 | "url-loader": "^0.5.7",
47 | "webpack": "^1.12.13"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/filters/factory.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {SelectButton} from './SelectButton';
3 | import {ConditionalSelectButton} from './ConditionalSelectButton';
4 | import {ApiButton} from './ApiButton';
5 | import {DownloadButton} from './DownloadButton';
6 | import {SliderInput} from './SliderInput';
7 | import {DynamicSearchInput} from './DynamicSearchInput';
8 | import {CheckboxGroup} from './CheckboxGroup';
9 |
10 | var FilterFactory = function(type) {
11 | if (typeof FilterFactory[type] != 'function'){
12 | throw new Error(type + ' is not a valid filter.');
13 | }
14 |
15 | return FilterFactory[type];
16 | };
17 |
18 | FilterFactory.SliderInput = SliderInput;
19 | FilterFactory.SelectButton = SelectButton;
20 | FilterFactory.ConditionalSelectButton = ConditionalSelectButton;
21 | FilterFactory.ApiButton = ApiButton;
22 | FilterFactory.DownloadButton = DownloadButton;
23 | FilterFactory.DynamicSearch = DynamicSearchInput;
24 | FilterFactory.CheckboxGroup = CheckboxGroup;
25 |
26 | export class Filter extends React.Component {
27 | constructor(props) {
28 | super(props);
29 | }
30 |
31 | render() {
32 | var Z = FilterFactory(this.props.type);
33 | return (
34 |
40 | );
41 | }
42 | }
43 |
44 | export {FilterFactory};
45 |
--------------------------------------------------------------------------------
/src/charts/nvd3/base.js:
--------------------------------------------------------------------------------
1 | import 'nvd3/build/nv.d3.min.css';
2 |
3 | import React from 'react';
4 | import TwoAxisFocus from './twoAxisFocus';
5 | import PieChart from './pieChart';
6 |
7 | var NVD3Factory = function(type, options){
8 | var chart;
9 |
10 | if (typeof NVD3Factory[type] != 'function'){
11 | throw new Error(type + ' is not a valid NVD3 type.');
12 | }
13 | NVD3Factory.prototype = NVD3Factory[type].prototype;
14 | chart = new NVD3Factory[type]();
15 | chart.initialize(options);
16 | return chart;
17 | }
18 | NVD3Factory.TwoAxisFocus = TwoAxisFocus;
19 | NVD3Factory.PieChart = PieChart;
20 |
21 | export class NVD3Chart extends React.Component {
22 | constructor(props) {
23 | super(props);
24 | this.state = {
25 | chart: null
26 | };
27 | }
28 |
29 | componentWillMount() {
30 | this.setState({
31 | chart: this._initialize(this.state.chart)
32 | });
33 | }
34 |
35 | _initialize(chart) {
36 | chart = NVD3Factory(this.props.options.type,
37 | this.props.options);
38 |
39 | chart.get(this.props.options.chartid,
40 | this.props.options.url,
41 | this.props.options.init_params);
42 |
43 | return chart;
44 | }
45 |
46 | _update(params) {
47 | this.state.chart.get(this.props.options.chartid,
48 | this.props.options.url,
49 | params);
50 | }
51 |
52 | render() {
53 | return (
54 |
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/charts/metricsgraphics/base.js:
--------------------------------------------------------------------------------
1 |
2 | import 'metrics-graphics/dist/metricsgraphics.css';
3 | import React from 'react';
4 | // import MG from 'metrics-graphics';
5 |
6 | export class MetricsGraphics extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | componentDidMount() {
12 | this._initialize(this.props.options.params.init_params);
13 | }
14 |
15 | _initialize(params) {
16 | d3.json(this.props.options.url.concat("?", $.param(params)),
17 | function(error, data){
18 |
19 | var options = this.props.options.params;
20 | if(data.date){
21 | for(var i=0; i < data.result.length; i++){
22 | data.result[i] = MG.convert.date(data.result[i], "x",
23 | this.props.options.date_format);
24 | }
25 | }
26 | options.data = data.result;
27 | MG.data_graphic(options);
28 | }.bind(this)
29 | );
30 | }
31 |
32 | _update(params) {
33 | d3.json(this.props.options.url.concat("?",$.param(params)),
34 | function(error, data){
35 | var options = this.props.options.params;
36 | if(data.date){
37 | for(var i=0; i < data.result.length; i++){
38 | data.result[i] = MG.convert.date(data.result[i], "x",
39 | this.props.options.date_format);
40 | }
41 | }
42 | options.data = data.result;
43 | MG.data_graphic(options);
44 | }.bind(this)
45 | );
46 | }
47 |
48 | render() {
49 | var width = {width: this.props.options.params.width};
50 |
51 | return (
52 |
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/layouts/FilterChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Button, Row} from 'react-bootstrap';
3 | import {Filter} from '../filters/factory';
4 | import {Chart} from '../charts/factory';
5 |
6 | export class FilterChart extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | _handleClick(input) {
12 | var params = {};
13 | for(var i = 0; i < this.props.filters.length; i++){
14 | var vals = this.refs["filter_".concat(i)].refs.filter.getCurrentState();
15 | for(var key in vals){
16 | params[key] = vals[key];
17 | }
18 | }
19 | if(input){
20 | for(var i = 0; i < input.length; i++){
21 | params[input[i].alias] = input[i].value;
22 | }
23 | }
24 | for(var i = 0; i < this.props.charts.length; i++){
25 | this.refs["chart_".concat(i)].update(params);
26 | }
27 | return params;
28 | }
29 |
30 | render() {
31 | var items = this.props.filters.map(function(x, index){
32 | return();
39 | }.bind(this));
40 |
41 | var charts = this.props.charts.map(function(x, index){
42 | return();
45 | });
46 |
47 |
48 | return (
49 |
50 |
51 |
52 | {items}
53 |
54 |
55 |
56 | {charts}
57 |
58 |
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/charts/datatables/base.js:
--------------------------------------------------------------------------------
1 | import 'datatables/media/css/jquery.dataTables.min.css';
2 | require("datatables");
3 | import React from 'react';
4 | $["dataTable"] = require("datatables");
5 |
6 | export class Table extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | columns: []
11 | };
12 | }
13 |
14 | componentWillMount() {
15 | this._initialize();
16 | }
17 |
18 | _initialize() {
19 | var _url = this.props.options.url.concat("?",
20 | $.param(this.props.options.params));
21 | $.get(_url,
22 | function(result){
23 | this.setState({columns: result.columns});
24 | var options = this.props.options.table_options;
25 | options.data = result.data;
26 | options.columns = result.columns;
27 | options.aaData = result.data;
28 | if("columnDefs" in this.props.options.table_options){
29 | options.columnDefs = this.props.options.table_options.columnDefs;
30 | options.columnDefs.forEach(function(x){
31 | x.render = new Function("return '" + x.render + "';");
32 | });
33 | }
34 |
35 | options.initComplete = new Function("settings", "json",
36 | this.props.options.table_options.initComplete);
37 | options.drawCallback = new Function("settings",
38 | this.props.options.table_options.drawCallback);
39 | $('#'.concat(this.props.options.id)).dataTable(options);
40 | }.bind(this)
41 | );
42 | }
43 |
44 | _update(params) {
45 | var _table = $('#'.concat(this.props.options.id)).dataTable();
46 | _table.api().ajax.url(
47 | this.props.options.url.concat("?",$.param(params))
48 | ).load();
49 |
50 | }
51 |
52 | render() {
53 | var header = this.state.columns.map(function(item, index) {
54 | return (
55 | {item.data}
56 | );
57 | });
58 | return (
59 |
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | path: './build',
7 | filename: 'pyxley.js',
8 | library: 'pxyley',
9 | libraryTarget: 'umd'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test: /\.js?$/,
15 | loader: 'babel',
16 | exclude: /(node_modules|bower_components)/,
17 | query: {
18 | presets: ['es2015', 'react']
19 | }
20 | },
21 | {
22 | test: /\.css$/,
23 | loaders: ['style', 'css', 'sass']
24 | },
25 | {
26 | test: /\.png$/,
27 | loader: "url-loader?mimetype=image/png"
28 | },
29 | {
30 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
31 | loader: "file"
32 | },
33 | {
34 | test: /\.(woff|woff2)$/,
35 | loader:"url?prefix=font/&limit=5000"
36 | },
37 | {
38 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
39 | loader: "url?limit=10000&mimetype=application/octet-stream"
40 | },
41 | {
42 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
43 | loader: "url?limit=10000&mimetype=image/svg+xml"
44 | }
45 |
46 | ]
47 | },
48 | externals: [
49 | {
50 | "plotly\.js": "Plotly"
51 | },
52 | {
53 | "datamaps": "Datamap"
54 | },
55 | {
56 | "metrics-graphics": "MG"
57 | },
58 | {
59 | "d3": "d3"
60 | },
61 | {
62 | "nvd3": "nv"
63 | },
64 | {
65 | 'react': {
66 | root: 'React',
67 | commonjs2: 'react',
68 | commonjs: 'react',
69 | amd: 'react'
70 | }
71 | },
72 | {
73 | 'react-dom': {
74 | root: 'ReactDOM',
75 | commonjs2: 'react-dom',
76 | commonjs: 'react-dom',
77 | amd: 'react-dom'
78 | }
79 | }
80 | ],
81 | plugins: [
82 | new webpack.ProvidePlugin({
83 | $: "jquery",
84 | jQuery: "jquery",
85 | "window.jQuery": "jquery",
86 | "window.$": "jquery"
87 | }),
88 | new webpack.DefinePlugin({
89 | "process.env": {
90 | NODE_ENV: JSON.stringify("production")
91 | }
92 | })
93 | ]
94 |
95 | };
96 |
--------------------------------------------------------------------------------
/src/filters/ConditionalSelectButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import BaseFilter from './BaseFilter';
3 | import {SelectButton} from './SelectButton';
4 | import {DropdownButton, MenuItem} from 'react-bootstrap';
5 |
6 | export class ConditionalSelectButton extends BaseFilter {
7 | constructor(props){
8 | super(props);
9 | }
10 |
11 | _handleClick(index, text, e) {
12 | e.preventDefault();
13 | this.setState({
14 | selected: index,
15 | value: text
16 | });
17 |
18 | if(this.props.dynamic){
19 | var result = [
20 | {
21 | alias: this.props.options.aliases[0],
22 | value: text
23 | },
24 | {
25 | alias: this.props.options.aliases[1],
26 | value: "All"
27 | }
28 | ];
29 | this.props.onChange(result);
30 | }
31 | this.refs.secondary.setState({value: "All"});
32 | }
33 |
34 | getCurrentState() {
35 | var result = {};
36 | result[this.props.options.aliases[0]] = this.state.value
37 | || this.props.options.defaults[0];
38 | result[this.props.options.aliases[1]] = this.refs.secondary.state.value
39 | || this.props.options.defaults[1];
40 | return result;
41 | }
42 |
43 | render() {
44 | var primary = this.props.options.items.map(function(item, idx){
45 | return (
46 |
49 | {item.primary}
50 |
51 | );
52 | }.bind(this));
53 |
54 | var label = this.state.value || this.props.options.labels[0];
55 | var secondary = {
56 | items: this.props.options.items[this.state.selected].secondary,
57 | label: this.props.options.labels[1],
58 | alias: this.props.options.aliases[1],
59 | default: this.props.options.defaults[1]
60 | };
61 | return (
62 |
63 |
67 | {primary}
68 |
69 |
73 |
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/filters/DynamicSearchInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {Input, Dropdown, MenuItem} from 'react-bootstrap';
4 | import classNames from 'classnames';
5 |
6 | class InputMenu extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | value: ''
11 | };
12 | this.onChange = e => this.setState({
13 | value: e.target.value
14 | });
15 | }
16 |
17 | filterChildren() {
18 | let { children } = this.props;
19 | let filtered = [];
20 | React.Children.forEach(children, child => {
21 | filtered.push(child);
22 | });
23 |
24 | return filtered;
25 | }
26 |
27 | render() {
28 |
29 | return (
30 |
31 |
32 |
33 | { this.filterChildren() }
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | export class DynamicSearchInput extends React.Component {
41 | constructor(props) {
42 | super(props);
43 | this.state = {
44 | value: null,
45 | searchString: "",
46 | items: []
47 | };
48 | }
49 |
50 | getCurrentState() {
51 | var result = {};
52 | result[this.props.options.alias] = this.state.value ||
53 | this.props.options.default;
54 | return result;
55 | }
56 |
57 | _fetchAPIData(params) {
58 | var url = this.props.options.url.concat("?", $.param(params));
59 | $.get(url, function(result){
60 | this.setState({items: result.data});
61 | }.bind(this));
62 | }
63 |
64 | update(params) {
65 | this._fetchAPIData(params);
66 | }
67 |
68 | componentDidMount() {
69 | this._fetchAPIData({});
70 | }
71 |
72 | _handleItemSelect(text, e) {
73 | this.setState({ value: text });
74 | this.dd.refs.inner.toggleOpen(false);
75 | if(this.props.dynamic){
76 | var result = {
77 | alias: this.props.options.alias,
78 | value: text
79 | };
80 | this.props.onChange([result]);
81 | }
82 |
83 | }
84 |
85 | _filterItems() {
86 | var items = this.state.items;
87 | var searchString = this.state.searchString.trim().toLowerCase();
88 | if(searchString.length > 0){
89 | items = items.filter(function(x){
90 | return x.toLowerCase().match(searchString)
91 | });
92 | }
93 | var nresults = items.length;
94 | var results_left = "Results Left: #";
95 | return items.map(function(item, index){
96 | if(index < this.props.options.max){
97 | return (
98 |
101 | {item}
102 |
103 |
104 | );
105 | } else if (index == this.props.options.max){
106 | return (
107 |
108 | {results_left.replace("#", (nresults-index))}
109 |
110 | );
111 | }
112 | }.bind(this));
113 | }
114 |
115 | _handleChange(e) {
116 | e.preventDefault();
117 | this.setState({
118 | searchString: e.target.value,
119 | value: e.target.value
120 | });
121 | }
122 |
123 | render() {
124 | return (
125 |
142 | );
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/charts/nvd3/twoAxisFocus.js:
--------------------------------------------------------------------------------
1 | // Adapted from nvd3.org Line With View Finder
2 | // http://nvd3.org/examples/lineWithFocus.html
3 | // and multichart http://nvd3.org/examples/linePlusBar.html
4 | import d3 from 'd3';
5 |
6 | const twoAxisFocusChart = function() {
7 | "use strict";
8 |
9 | //============================================================
10 | // Public Variables with Default Settings
11 | //------------------------------------------------------------
12 |
13 | var yScale1 = d3.scale.linear()
14 | , yScale2 = d3.scale.linear()
15 | , yScaleC = d3.scale.linear()
16 | , lines1 = nv.models.line().yScale(yScale1)
17 | , lines2 = nv.models.line().yScale(yScale2)
18 | , linesC = nv.models.line().yScale(yScaleC)
19 | , xAxis = nv.models.axis().orient('bottom').tickPadding(5)
20 | , y1Axis = nv.models.axis().scale(yScale1).orient('left')
21 | , y2Axis = nv.models.axis().scale(yScale2).orient('right')
22 | , xCAxis = nv.models.axis().orient('bottom').tickPadding(5)
23 | , yCAxis = nv.models.axis().scale(yScaleC).orient('left')
24 | , legend = nv.models.legend()
25 | , brush = d3.svg.brush()
26 |
27 | ;
28 |
29 | var margin = {top: 30, right: 30, bottom: 30, left: 60}
30 | , margin2 = {top: 10, right: 30, bottom: 20, left: 60}
31 | , color = nv.utils.defaultColor()
32 | , width = null
33 | , height = null
34 | , height2 = 100
35 | , x
36 | , y1
37 | , y2
38 | , xC
39 | , yC
40 | , showLegend = true
41 | , brushExtent = null
42 | , tooltips = true
43 | , tooltip = nv.models.tooltip()
44 | // , tooltip = function(key, x, y, e, graph) {
45 | // return '' + key + ' ' +
46 | // '' + y + ' at ' + x + '
'
47 | // }
48 | , noData = "No Data Available."
49 | , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
50 | , transitionDuration = 250
51 | , state = nv.utils.state()
52 | , defaultState = null
53 | , yDomain1
54 | , yDomain2
55 | , yDomainC
56 | ;
57 |
58 | lines1.clipEdge(true);
59 | lines2.clipEdge(true);
60 | linesC.interactive(false).clipEdge(true);
61 |
62 | //============================================================
63 | // Private Variables
64 | //------------------------------------------------------------
65 |
66 | var showTooltip = function(e, offsetElement) {
67 | var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
68 | top = e.pos[1] + ( offsetElement.offsetTop || 0),
69 | x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
70 | y = y1Axis.tickFormat()(lines1.y()(e.point, e.pointIndex)),
71 | content = tooltip(e.series.key, x, y, e, chart);
72 | };
73 |
74 |
75 |
76 |
77 | var stateGetter = function(data) {
78 | return function(){
79 | return {
80 | active: data.map(function(d) { return !d.disabled })
81 | };
82 | }
83 | };
84 |
85 | var stateSetter = function(data) {
86 | return function(state) {
87 | if (state.active !== undefined)
88 | data.forEach(function(series,i) {
89 | series.disabled = !state.active[i];
90 | });
91 | }
92 | };
93 |
94 | function chart(selection) {
95 | selection.each(function(data) {
96 |
97 |
98 | var container = d3.select(this),
99 | that = this;
100 | nv.utils.initSVG(container);
101 | var availableWidth = (width || parseInt(container.style('width')) || 960)
102 | - margin.left - margin.right,
103 | availableHeight1 = (height || parseInt(container.style('height')) || 400)
104 | - margin.top - margin.bottom - height2,
105 | availableHeight2 = height2 - margin2.top - margin2.bottom;
106 |
107 | var dataLines1 = data.filter(function(d) {return d.yAxis == 1});
108 | var dataLines2 = data.filter(function(d) {return d.yAxis == 2});
109 |
110 | chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
111 | chart.container = this;
112 |
113 | state
114 | .setter(stateSetter(data), chart.update)
115 | .getter(stateGetter(data))
116 | .update();
117 |
118 | // DEPRECATED set state.disableddisabled
119 | state.disabled = data.map(function(d) { return !!d.disabled });
120 |
121 | if (!defaultState) {
122 | var key;
123 | defaultState = {};
124 | for (key in state) {
125 | if (state[key] instanceof Array)
126 | defaultState[key] = state[key].slice(0);
127 | else
128 | defaultState[key] = state[key];
129 | }
130 | }
131 |
132 | // Display No Data message if there's nothing to show.
133 | if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
134 | var noDataText = container.selectAll('.nv-noData').data([noData]);
135 |
136 | noDataText.enter().append('text')
137 | .attr('class', 'nvd3 nv-noData')
138 | .attr('dy', '-.7em')
139 | .style('text-anchor', 'middle');
140 |
141 | noDataText
142 | .attr('x', margin.left + availableWidth / 2)
143 | .attr('y', margin.top + availableHeight1 / 2)
144 | .text(function(d) { return d });
145 |
146 | return chart;
147 | } else {
148 | container.selectAll('.nv-noData').remove();
149 | }
150 |
151 | var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
152 | .map(function(d) {
153 | return d.values.map(function(d,i) {
154 | return { x: d.x, y: d.y }
155 | })
156 | });
157 |
158 | var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
159 | .map(function(d) {
160 | return d.values.map(function(d,i) {
161 | return { x: d.x, y: d.y }
162 | })
163 | });
164 |
165 | // Setup Scales
166 | x = lines1.xScale();
167 | y1 = lines1.yScale();
168 | y2 = lines2.yScale();
169 | xC = linesC.xScale();
170 | yC = linesC.yScale();
171 |
172 | // x.domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
173 | // .range([0, availableWidth]);
174 |
175 | // Setup containers and skeleton of chart
176 | var wrap = container.selectAll('g.nv-wrap.nv-twoAxisFocusChart').data([data]);
177 | var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-twoAxisFocusChart').append('g');
178 | var g = wrap.select('g');
179 | gEnter.append('g').attr('class', 'legendWrap');
180 |
181 | var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
182 | focusEnter.append('g').attr('class', 'nv-x nv-axis');
183 | focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
184 | focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
185 | focusEnter.append('g').attr('class', 'nv-lines1Wrap');
186 | focusEnter.append('g').attr('class', 'nv-lines2Wrap');
187 |
188 | var contextEnter = gEnter.append('g').attr('class', 'nv-context');
189 | contextEnter.append('g').attr('class', 'nv-x nv-axis');
190 | contextEnter.append('g').attr('class', 'nv-y nv-axis');
191 | contextEnter.append('g').attr('class', 'nv-lines1Wrap');
192 | contextEnter.append('g').attr('class', 'nv-brushBackground');
193 | contextEnter.append('g').attr('class', 'nv-x nv-brush');
194 |
195 | // Legend
196 | var color_array = data.map(function(d,i) {
197 | return data[i].color || color(d, i);
198 | });
199 |
200 | if (showLegend) {
201 | legend.color(color_array);
202 | legend.width( availableWidth / 2 );
203 |
204 | g.select('.legendWrap')
205 | .datum(data.map(function(series) {
206 | series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
207 | series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
208 | return series;
209 | }))
210 | .call(legend);
211 |
212 | if ( margin.top != legend.height()) {
213 | margin.top = legend.height();
214 | availableHeight1 = (height || parseInt(container.style('height')) || 400)
215 | - margin.top - margin.bottom - height2;
216 | availableHeight2 = height2 - margin2.top - margin2.bottom;
217 | // availableHeight = (height || parseInt(container.style('height')) || 400)
218 | // - margin.top - margin.bottom;
219 | }
220 |
221 | g.select('.legendWrap')
222 | .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
223 | }
224 |
225 | wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
226 |
227 | // Main Chart Component(s)
228 | lines1
229 | .width(availableWidth)
230 | .height(availableHeight1)
231 | .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1}));
232 | lines2
233 | .width(availableWidth)
234 | .height(availableHeight1)
235 | .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2}));
236 | linesC
237 | .width(availableWidth)
238 | .height(availableHeight2)
239 | .color(color_array.filter(function(d,i) { return data[i].yAxis == 2}));
240 |
241 | g.select('.nv-context')
242 | .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
243 |
244 | yScale1.domain(yDomain1 || d3.extent(series1, function(d) { return d.y } ))
245 | .range([0, availableHeight1]);
246 |
247 | yScale2.domain(yDomain2 || d3.extent(series2, function(d) { return d.y } ))
248 | .range([0, availableHeight1]);
249 |
250 | yScaleC.domain(yDomainC || d3.extent(series2, function(d) { return d.y } ))
251 | .range([availableHeight2, 0]);
252 |
253 | lines1.yDomain(yScale1.domain());
254 | lines2.yDomain(yScale2.domain());
255 | linesC.yDomain(yScaleC.domain());
256 |
257 | var context1LinesWrap = g.select('.nv-context .nv-lines1Wrap')
258 | .datum(dataLines2);
259 |
260 | if(dataLines2.length){d3.transition(context1LinesWrap).call(linesC);}
261 |
262 | // Setup Main (Focus) Axes
263 | xAxis
264 | .scale(x)
265 | .ticks( nv.utils.calcTicksX(availableWidth/100, dataLines1) )
266 | .tickSize(-availableHeight1, 0);
267 |
268 | y1Axis
269 | .ticks( nv.utils.calcTicksY(availableHeight1/36, dataLines1) )
270 | .tickSize( -availableWidth, 0);
271 |
272 | y2Axis
273 | .ticks( nv.utils.calcTicksY(availableHeight1/36, dataLines2) )
274 | .tickSize( -availableWidth, 0);
275 |
276 | g.select('.nv-focus .nv-x.nv-axis')
277 | .attr('transform', 'translate(0,' + availableHeight1 + ')');
278 |
279 |
280 | function mouseover_line(evt) {
281 | var yaxis = data[evt.seriesIndex].yAxis === 2 ? y2Axis : y1Axis;
282 | evt.value = evt.point.x;
283 | evt.series = {
284 | value: evt.point.y,
285 | color: evt.point.color
286 | };
287 | tooltip
288 | .duration(100)
289 | .valueFormatter(function(d, i) {
290 | return yaxis.tickFormat()(d, i);
291 | })
292 | .data(evt)
293 | .position(evt.pos)
294 | .hidden(false);
295 | }
296 |
297 | // Setup Brush
298 | brush
299 | .x(xC)
300 | .on('brush', function() {
301 | //When brushing, turn off transitions because chart needs to change immediately.
302 | var oldTransition = chart.duration();
303 | chart.duration(0);
304 | onBrush();
305 | chart.duration(oldTransition);
306 | });
307 |
308 | if (brushExtent) brush.extent(brushExtent);
309 |
310 | var brushBG = g.select('.nv-brushBackground').selectAll('g')
311 | .data([brushExtent || brush.extent()])
312 |
313 | var brushBGenter = brushBG.enter()
314 | .append('g');
315 |
316 | brushBGenter.append('rect')
317 | .attr('class', 'left')
318 | .attr('x', 0)
319 | .attr('y', 0)
320 | .attr('height', availableHeight2);
321 |
322 | brushBGenter.append('rect')
323 | .attr('class', 'right')
324 | .attr('x', 0)
325 | .attr('y', 0)
326 | .attr('height', availableHeight2);
327 |
328 | var gBrush = g.select('.nv-x.nv-brush')
329 | .call(brush);
330 | gBrush.selectAll('rect')
331 | //.attr('y', -5)
332 | .attr('height', availableHeight2);
333 | gBrush.selectAll('.resize').append('path').attr('d', resizePath);
334 |
335 | onBrush();
336 |
337 | // Setup Secondary (Context) Axes
338 | xCAxis
339 | .scale(xC)
340 | .ticks( nv.utils.calcTicksX(availableWidth/100, dataLines2) )
341 | .tickSize(-availableHeight2, 0);
342 |
343 | g.select('.nv-context .nv-x.nv-axis')
344 | .attr('transform', 'translate(0,' + yScaleC.range()[0] + ')');
345 | d3.transition(g.select('.nv-context .nv-x.nv-axis'))
346 | .call(xCAxis);
347 |
348 | yCAxis
349 | .ticks( nv.utils.calcTicksY(availableHeight2/36, dataLines2) )
350 | .tickSize( -availableWidth, 0);
351 |
352 | d3.transition(g.select('.nv-context .nv-y.nv-axis'))
353 | .call(yCAxis);
354 |
355 | // g.select('.nv-context .nv-y.nv-axis').transition().duration(transitionDuration)
356 | // .call(yCAxis);
357 |
358 | g.select('.nv-context .nv-x.nv-axis')
359 | .attr('transform', 'translate(0,' + yScaleC.range()[0] + ')');
360 |
361 | //============================================================
362 | // Event Handling/Dispatching (in chart's scope)
363 | //------------------------------------------------------------
364 |
365 | legend.dispatch.on('stateChange', function(newState) {
366 | for (var key in newState)
367 | state[key] = newState[key];
368 | dispatch.stateChange(state);
369 | chart.update();
370 | });
371 |
372 |
373 | // dispatch.on('tooltipShow', function(e) {
374 | // if (tooltips) showTooltip(e, that.parentNode);
375 | // });
376 |
377 | dispatch.on('changeState', function(e) {
378 | if (typeof e.disabled !== 'undefined') {
379 | data.forEach(function(series,i) {
380 | series.disabled = e.disabled[i];
381 | });
382 | }
383 | chart.update();
384 | });
385 |
386 | //============================================================
387 | // Functions
388 | //------------------------------------------------------------
389 |
390 | // Taken from crossfilter (http://square.github.com/crossfilter/)
391 | function resizePath(d) {
392 | var e = +(d == 'e'),
393 | x = e ? 1 : -1,
394 | y = availableHeight2 / 3;
395 | return 'M' + (.5 * x) + ',' + y
396 | + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
397 | + 'V' + (2 * y - 6)
398 | + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
399 | + 'Z'
400 | + 'M' + (2.5 * x) + ',' + (y + 8)
401 | + 'V' + (2 * y - 8)
402 | + 'M' + (4.5 * x) + ',' + (y + 8)
403 | + 'V' + (2 * y - 8);
404 | }
405 |
406 |
407 | function updateBrushBG() {
408 | if (!brush.empty()) brush.extent(brushExtent);
409 | brushBG
410 | .data([brush.empty() ? xC.domain() : brushExtent])
411 | .each(function(d,i) {
412 | var leftWidth = xC(d[0]) - x.range()[0],
413 | rightWidth = x.range()[1] - xC(d[1]);
414 | d3.select(this).select('.left')
415 | .attr('width', leftWidth < 0 ? 0 : leftWidth);
416 |
417 | d3.select(this).select('.right')
418 | .attr('x', xC(d[1]))
419 | .attr('width', rightWidth < 0 ? 0 : rightWidth);
420 | });
421 | }
422 |
423 |
424 | function onBrush() {
425 | brushExtent = brush.empty() ? null : brush.extent();
426 | var extent = brush.empty() ? xC.domain() : brush.extent();
427 |
428 | //The brush extent cannot be less than one. If it is, don't update the line chart.
429 | if (Math.abs(extent[0] - extent[1]) <= 1) {
430 | return;
431 | }
432 |
433 | dispatch.brush({extent: extent, brush: brush});
434 |
435 |
436 | updateBrushBG();
437 |
438 | // Update Main (Focus)
439 | var focusLines1Wrap = g.select('.nv-focus .nv-lines1Wrap')
440 | .datum(
441 | dataLines1
442 | .filter(function(d) { return !d.disabled })
443 | .map(function(d,i) {
444 | return {
445 | key: d.key,
446 | area: d.area,
447 | values: d.values.filter(function(d,i) {
448 | return lines1.x()(d,i) >= extent[0] && lines1.x()(d,i) <= extent[1];
449 | })
450 | }
451 | })
452 | );
453 |
454 | var focusLines2Wrap = g.select('.nv-focus .nv-lines2Wrap')
455 | .datum(
456 | dataLines2
457 | .filter(function(d) { return !d.disabled })
458 | .map(function(d,i) {
459 | return {
460 | key: d.key,
461 | area: d.area,
462 | values: d.values.filter(function(d,i) {
463 | return lines2.x()(d,i) >= extent[0] && lines2.x()(d,i) <= extent[1];
464 | })
465 | }
466 | })
467 | );
468 | focusLines1Wrap.transition().duration(transitionDuration).call(lines1);
469 | focusLines2Wrap.transition().duration(transitionDuration).call(lines2);
470 |
471 |
472 | // Update Main (Focus) Axes
473 | g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
474 | .call(xAxis);
475 | g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
476 | .call(y1Axis);
477 | g.select('.nv-focus .nv-y2.nv-axis')
478 | .attr('transform', 'translate(' + x.range()[1] + ',0)')
479 | .transition().duration(transitionDuration)
480 | .call(y2Axis);
481 | }
482 | lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
483 | lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
484 | lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
485 | tooltip.hidden(true)
486 | });
487 | lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
488 | tooltip.hidden(true)
489 | });
490 | });
491 |
492 | return chart;
493 | }
494 |
495 | //============================================================
496 | // Event Handling/Dispatching (out of chart's scope)
497 | //------------------------------------------------------------
498 |
499 | // lines1.dispatch.on('elementMouseover.tooltip', function(e) {
500 | // e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
501 | // dispatch.tooltipShow(e);
502 | // });
503 |
504 | // lines1.dispatch.on('elementMouseout.tooltip', function(e) {
505 | // dispatch.tooltipHide(e);
506 | // });
507 |
508 | // lines2.dispatch.on('elementMouseover.tooltip', function(e) {
509 | // e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
510 | // dispatch.tooltipShow(e);
511 | // });
512 |
513 | // lines2.dispatch.on('elementMouseout.tooltip', function(e) {
514 | // dispatch.tooltipHide(e);
515 | // });
516 |
517 | // dispatch.on('tooltipHide', function() {
518 | // if (tooltips) nv.tooltip.cleanup();
519 | // });
520 |
521 | //============================================================
522 | // Expose Public Variables
523 | //------------------------------------------------------------
524 |
525 | // expose chart's sub-components
526 | chart.dispatch = dispatch;
527 | chart.legend = legend;
528 | chart.lines1 = lines1;
529 | chart.lines2 = lines2;
530 | chart.linesC = linesC;
531 | chart.xAxis = xAxis;
532 | chart.y1Axis = y1Axis;
533 | chart.y2Axis = y2Axis;
534 | chart.xCAxis = xCAxis;
535 | chart.yCAxis = yCAxis;
536 |
537 | chart.options = nv.utils.optionsFunc.bind(chart);
538 |
539 | chart._options = Object.create({}, {
540 | // simple options, just get/set the necessary values
541 | width: {get: function(){return width;}, set: function(_){width=_;}},
542 | height: {get: function(){return height;}, set: function(_){height=_;}},
543 | focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}},
544 | showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
545 | yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
546 | yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
547 | yDomainC: {get: function(){return yDomainC;}, set: function(_){yDomainC=_;}},
548 | brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
549 | tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
550 | tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
551 | defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
552 | noData: {get: function(){return noData;}, set: function(_){noData=_;}},
553 |
554 | // options that require extra logic in the setter
555 | margin: {get: function(){return margin;}, set: function(_){
556 | margin.top = _.top !== undefined ? _.top : margin.top;
557 | margin.right = _.right !== undefined ? _.right : margin.right;
558 | margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
559 | margin.left = _.left !== undefined ? _.left : margin.left;
560 | }},
561 | color: {get: function(){return color;}, set: function(_){
562 | color = nv.utils.getColor(_);
563 | legend.color(color);
564 | // line color is handled above?
565 | }},
566 | interpolate: {get: function(){return lines1.interpolate();}, set: function(_){
567 | lines1.interpolate(_);
568 | lines2.interpolate(_);
569 | linesC.interpolate(_);
570 | }},
571 | xTickFormat: {get: function(){return xAxis.xTickFormat();}, set: function(_){
572 | xAxis.xTickFormat(_);
573 | xCAxis.xTickFormat(_);
574 | }},
575 | yTickFormat: {get: function(){return y1Axis.yTickFormat();}, set: function(_){
576 | y1Axis.yTickFormat(_);
577 | y2Axis.yTickFormat(_);
578 | yCAxis.yTickFormat(_);
579 | }},
580 | duration: {get: function(){return transitionDuration;}, set: function(_){
581 | transitionDuration=_;
582 | y1Axis.duration(transitionDuration);
583 | y2Axis.duration(transitionDuration);
584 | yCAxis.duration(transitionDuration);
585 | xAxis.duration(transitionDuration);
586 | }},
587 | x: {get: function(){return lines.x();}, set: function(_){
588 | lines1.x(_);
589 | lines2.x(_);
590 | linesC.x(_);
591 | }},
592 | y: {get: function(){return lines.y();}, set: function(_){
593 | lines1.y(_);
594 | lines2.y(_);
595 | linesC.y(_);
596 | }}
597 | });
598 |
599 | nv.utils.initOptions(chart);
600 |
601 | return chart;
602 | };
603 |
604 | var TwoAxisFocus = function() {
605 | this.chart = twoAxisFocusChart();
606 | };
607 |
608 | TwoAxisFocus.prototype.initialize = function(options){
609 | this.chart.margin(options.margin)
610 | .color(options.colors);
611 |
612 | this.chart.lines1.interpolate('monotone');
613 | this.chart.lines2.interpolate('monotone');
614 | this.chart.linesC.interpolate('monotone');
615 |
616 | this.chart.xAxis
617 | .showMaxMin(false)
618 | .axisLabel(options.labels.xAxis)
619 | .tickFormat(function(d){return d3.time.format('%X')(new Date(d));});
620 |
621 | this.chart.xCAxis
622 | .showMaxMin(false)
623 | .axisLabel(options.labels.xAxis)
624 | .tickFormat(function(d){return d3.time.format('%X')(new Date(d));});
625 |
626 | this.chart.y1Axis
627 | .axisLabel(options.labels.yAxis1)
628 | .tickFormat(d3.format(',.2f'));
629 |
630 | this.chart.y2Axis
631 | .axisLabel(options.labels.yAxis2)
632 | .tickFormat(d3.format(',.2f'));
633 |
634 | this.chart.yCAxis
635 | .axisLabel(options.labels.yAxis2)
636 | .tickFormat(d3.format(',.2f'));
637 | };
638 |
639 | TwoAxisFocus.prototype.get = function(chartid, url, params){
640 | d3.json(url.concat("?", $.param(params)),
641 | function(error, result) {
642 | result.data.forEach(function(d){
643 | d.values.forEach(function(v){
644 | v.x = new Date(v.x * 1000);
645 | });
646 | });
647 | this.chart.yDomain1(result.yAxis1.bounds);
648 | this.chart.yDomain2(result.yAxis2.bounds);
649 | this.chart.yDomainC(result.yAxis2.bounds);
650 | d3.select("#".concat(chartid, " svg"))
651 | .datum(result.data)
652 | .call(this.chart);
653 | nv.utils.windowResize(this.chart.update);
654 |
655 | }.bind(this));
656 | };
657 |
658 | export default TwoAxisFocus;
659 |
--------------------------------------------------------------------------------