<commit_before>const React = require('react');
const h = require('react-hyperscript');
const Table = require('react-table');

const queryString = require('query-string');
const color = require('color');
const _ = require('lodash');
const classNames = require('classnames');

const make_cytoscape = require('../../common/cy');

const Icon = require('../../common/components').Icon;
const { apiCaller, PathwayCommonsService } = require('../../services');

const analysisFunctions = [
  {
    name: 'mean',
    func: _.mean,
  },
  {
    name: 'count',
    func: _.countBy,
  },
  {
    name: 'min',
    func: _.min
  },
  {
    name: 'max',
    func: _.max
  }
];

class OmniBar extends React.Component {
  render() {
    const props = this.props;
    return h('div.paint-omnibar', [
      h('a', { onClick: e => props.onMenuClick(e) }, [
        h(Icon, { icon: 'menu' }, 'click')
      ]),
      h('h5', `${props.name} | ${props.datasource}`)
    ]);
  }
}

// props
// expression table
// cy
class Network extends React.Component {
  componentWillUnmount() {
    this.state.cy.destroy();
  }

  componentDidMount() {
    const props = this.props;
    const container = document.getElementById('cy-container');
    props.cy.mount(container);
  }

  colourMap(val, min, max) {
    const distToMax = Math.abs(val - max);
    const distToMin = Math.abs(val - min);

    let hVal, lVal;
    if (distToMax > distToMin) {
      hVal = 240;
      lVal = ( val / Math.abs(max) ) * 100 + 20;
    } else {
      hVal = 0;
      lVal = ( val / Math.abs(max) ) * 100 - 20;
    }
    return color.hsl(hVal, 100, lVal).string();
  }

  percentToColour(percent) {
    const hslValue = ( 1 - percent ) * 240;

    return color.hsl(hslValue, 100, 50).string();
  }

  componentWillReceiveProps(nextProps) {
    if (!_.isEmpty(nextProps.expressionTable)) {
      this.applyExpressionData(nextProps);
    }
  }

  applyExpressionData(props) {
    const expressionTable = props.expressionTable;
    const networkNodes = _.uniq(props.cy.nodes('[class="macromolecule"]').map(node => node.data('label'))).sort();

    const expressionsInNetwork = expressionTable.rows.filter(row => networkNodes.includes(row.geneName));
    const maxVal = _.max(expressionsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(expressionsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    expressionsInNetwork.forEach(expression => {
      // probably more efficient to add the expression data to the node field instead of interating twice
      props.cy.nodes().filter(node => node.data('label') === expression.geneName).forEach(node => {

        const numSlices = expression.classValues.length > 16 ? 16 : expression.classValues.length;

        const pieStyle = {
          'pie-size': '100%'
        };
        for (let i = 1; i <= numSlices; i++) {

          pieStyle['pie-' + i + '-background-size'] = 100 / numSlices;
          pieStyle['pie-' + i + '-background-opacity'] = 1;
          pieStyle['pie-' + i + '-background-color'] = this.percentToColour(expression.classValues[i-1] / maxVal);
        }

        node.style(pieStyle);
      });
    });
  }

  render() {
    return h('div.paint-graph', [ h(`div.#cy-container`, {style: {width: '100vw', height: '100vh'}}) ]);
  }

}

class Paint extends React.Component {
  constructor(props) {
    super(props);

    const cy = make_cytoscape({headless: true});

    this.state = {
      rawEnrichmentData: {},
      expressionTable: {},
      cy: cy,
      name: '',
      datasource: '',
      drawerOpen: false
    };

    const query = queryString.parse(props.location.search);
    const enrichmentsURI = query.uri ? query.uri : null;

    if (enrichmentsURI != null) {
      fetch(enrichmentsURI)
        .then(response => response.json())
        .then(json => {
          this.setState({rawEnrichmentData: json});

          const expressionClasses = _.get(json.dataSetClassList, '0.classes', []);
          const expressions = _.get(json.dataSetExpressionList, '0.expressions', []);
          const searchParam = query.q;
          this.initPainter(expressions, expressionClasses, searchParam);

        });
    }
  }

  componentWillUnmount() {
    this.state.cy.destroy();
  }

  // only call this after you know component is mounted
  initPainter(expressions, expressionClasses, queryParam) {
    const state = this.state;
    const query = {
      q: queryParam,
      gt: 2,
      lt: 100,
      type: 'Pathway',
      datasource: 'reactome'
    };

    apiCaller.querySearch(query)
      .then(searchResults => {
        const uri = _.get(searchResults, '0.uri', null);

        if (uri != null) {
          apiCaller.getGraphAndLayout(uri, 'latest').then(response => {
            this.setState({
              name: _.get(response, 'graph.pathwayMetadata.title', 'N/A'),
              datasource: _.get(response, 'graph.pathwayMetadata.dataSource.0', 'N/A'),
            });

            state.cy.remove('*');
            state.cy.add({
              nodes: _.get(response, 'graph.nodes', []),
              edges: _.get(response, 'graph.edges', [])
            });

            state.cy.layout({
              name: 'cose-bilkent',
              randomize: false,
              nodeDimensionsIncludeLabels: true
            }).run();

            const expressionTable = {};

            const header = _.uniq(expressionClasses);

            expressionTable.header = header;
            expressionTable.rows = expressions.map(expression => {
              const geneName = expression.geneName;
              const values = expression.values;

              const class2ValuesMap = new Map();

              for (let expressionClass of header) {
                class2ValuesMap.set(expressionClass, []);
              }

              for (let i = 0; i < values.length; i++) {
                class2ValuesMap.get(expressionClasses[i]).push(values[i]);
              }

              return { geneName: geneName, classValues: Array.from(class2ValuesMap.entries()).map((entry =>  _.mean(entry[1]).toFixed(2))) };
            });

            this.setState({
              expressionTable: expressionTable
            });
          });
        }
    });
  }

  toggleDrawer() {
    this.setState({drawerOpen: !this.state.drawerOpen});
  }

  render() {
    const state = this.state;

    const expressionTable = state.expressionTable;

    const networkNodes = state.cy.nodes().map(node => node.data('label'));
    const enrichmentsInNetwork = _.get(expressionTable, 'rows', []).filter(row => networkNodes.includes(row.geneName));

    const maxVal = _.max(enrichmentsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(enrichmentsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    const expressionTableHeader = [h('th', '')].concat(_.get(expressionTable, 'header', []).map(column => h('th', column)));
    const expressionTableBody = _.sortBy(
      _.get(expressionTable, 'rows', []), (o) => o.geneName
    ).map(row => h('tr',[h('td', row.geneName)].concat(row.classValues.map(cv => h('td', cv)))));

    return h('div.paint', [
      h('div.paint-content', [
        h('div', { className: classNames('paint-drawer', { 'closed': !state.drawerOpen }) }, [
          h('a', { onClick: e => this.toggleDrawer()}, [
            h(Icon, { icon: 'close'}),
          ]),
          h('div.paint-legend', [
            h('p', `low ${minVal}`),
            h('p', `high ${maxVal}`)
          ]),
          h('p', `columns correspond to the clockwise direction on the pie (first column starts at 12 O'Clock going clockwise)`),
          h('table', [
            h('thead', [
              h('tr', expressionTableHeader)
            ]),
            h('tbody', expressionTableBody)
          ])

        ]),
        h(OmniBar, { name: state.name, datasource: state.datasource, onMenuClick: (e) => this.toggleDrawer() }),
        h(Network, { cy: state.cy, expressionTable: state.expressionTable })
      ])
    ]);
  }
}

module.exports = Paint;<commit_msg>Use react table for a sortable, paginated table<commit_after>const React = require('react');
const h = require('react-hyperscript');
const Table = require('react-table').default;

const queryString = require('query-string');
const color = require('color');
const _ = require('lodash');
const classNames = require('classnames');

const make_cytoscape = require('../../common/cy');

const Icon = require('../../common/components').Icon;
const { apiCaller, PathwayCommonsService } = require('../../services');

const analysisFunctions = [
  {
    name: 'mean',
    func: _.mean,
  },
  {
    name: 'count',
    func: _.countBy,
  },
  {
    name: 'min',
    func: _.min
  },
  {
    name: 'max',
    func: _.max
  }
];

class OmniBar extends React.Component {
  render() {
    const props = this.props;
    return h('div.paint-omnibar', [
      h('a', { onClick: e => props.onMenuClick(e) }, [
        h(Icon, { icon: 'menu' }, 'click')
      ]),
      h('h5', `${props.name} | ${props.datasource}`)
    ]);
  }
}

// props
// expression table
// cy
class Network extends React.Component {
  componentWillUnmount() {
    this.state.cy.destroy();
  }

  componentDidMount() {
    const props = this.props;
    const container = document.getElementById('cy-container');
    props.cy.mount(container);
  }

  colourMap(val, min, max) {
    const distToMax = Math.abs(val - max);
    const distToMin = Math.abs(val - min);

    let hVal, lVal;
    if (distToMax > distToMin) {
      hVal = 240;
      lVal = ( val / Math.abs(max) ) * 100 + 20;
    } else {
      hVal = 0;
      lVal = ( val / Math.abs(max) ) * 100 - 20;
    }
    return color.hsl(hVal, 100, lVal).string();
  }

  percentToColour(percent) {
    const hslValue = ( 1 - percent ) * 240;

    return color.hsl(hslValue, 100, 50).string();
  }

  componentWillReceiveProps(nextProps) {
    if (!_.isEmpty(nextProps.expressionTable)) {
      this.applyExpressionData(nextProps);
    }
  }

  applyExpressionData(props) {
    const expressionTable = props.expressionTable;
    const networkNodes = _.uniq(props.cy.nodes('[class="macromolecule"]').map(node => node.data('label'))).sort();

    const expressionsInNetwork = expressionTable.rows.filter(row => networkNodes.includes(row.geneName));
    const maxVal = _.max(expressionsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(expressionsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    expressionsInNetwork.forEach(expression => {
      // probably more efficient to add the expression data to the node field instead of interating twice
      props.cy.nodes().filter(node => node.data('label') === expression.geneName).forEach(node => {

        const numSlices = expression.classValues.length > 16 ? 16 : expression.classValues.length;

        const pieStyle = {
          'pie-size': '100%'
        };
        for (let i = 1; i <= numSlices; i++) {

          pieStyle['pie-' + i + '-background-size'] = 100 / numSlices;
          pieStyle['pie-' + i + '-background-opacity'] = 1;
          pieStyle['pie-' + i + '-background-color'] = this.percentToColour(expression.classValues[i-1] / maxVal);
        }

        node.style(pieStyle);
      });
    });
  }

  render() {
    return h('div.paint-graph', [ h(`div.#cy-container`, {style: {width: '100vw', height: '100vh'}}) ]);
  }

}

class Paint extends React.Component {
  constructor(props) {
    super(props);

    const cy = make_cytoscape({headless: true});

    this.state = {
      rawEnrichmentData: {},
      expressionTable: {},
      cy: cy,
      name: '',
      datasource: '',
      drawerOpen: false
    };

    const query = queryString.parse(props.location.search);
    const enrichmentsURI = query.uri ? query.uri : null;

    if (enrichmentsURI != null) {
      fetch(enrichmentsURI)
        .then(response => response.json())
        .then(json => {
          this.setState({rawEnrichmentData: json});

          const expressionClasses = _.get(json.dataSetClassList, '0.classes', []);
          const expressions = _.get(json.dataSetExpressionList, '0.expressions', []);
          const searchParam = query.q;
          this.initPainter(expressions, expressionClasses, searchParam);

        });
    }
  }

  componentWillUnmount() {
    this.state.cy.destroy();
  }

  // only call this after you know component is mounted
  initPainter(expressions, expressionClasses, queryParam) {
    const state = this.state;
    const query = {
      q: queryParam,
      gt: 2,
      lt: 100,
      type: 'Pathway',
      datasource: 'reactome'
    };

    apiCaller.querySearch(query)
      .then(searchResults => {
        const uri = _.get(searchResults, '0.uri', null);

        if (uri != null) {
          apiCaller.getGraphAndLayout(uri, 'latest').then(response => {
            this.setState({
              name: _.get(response, 'graph.pathwayMetadata.title', 'N/A'),
              datasource: _.get(response, 'graph.pathwayMetadata.dataSource.0', 'N/A'),
            });

            state.cy.remove('*');
            state.cy.add({
              nodes: _.get(response, 'graph.nodes', []),
              edges: _.get(response, 'graph.edges', [])
            });

            state.cy.layout({
              name: 'cose-bilkent',
              randomize: false,
              nodeDimensionsIncludeLabels: true
            }).run();

            const expressionTable = {};

            const header = _.uniq(expressionClasses);

            expressionTable.header = header;
            expressionTable.rows = expressions.map(expression => {
              const geneName = expression.geneName;
              const values = expression.values;

              const class2ValuesMap = new Map();

              for (let expressionClass of header) {
                class2ValuesMap.set(expressionClass, []);
              }

              for (let i = 0; i < values.length; i++) {
                class2ValuesMap.get(expressionClasses[i]).push(values[i]);
              }

              return { geneName: geneName, classValues: Array.from(class2ValuesMap.entries()).map((entry =>  _.mean(entry[1]).toFixed(2))) };
            });

            this.setState({
              expressionTable: expressionTable
            });
          });
        }
    });
  }

  toggleDrawer() {
    this.setState({drawerOpen: !this.state.drawerOpen});
  }

  render() {
    const state = this.state;

    const expressionTable = state.expressionTable;

    const networkNodes = state.cy.nodes().map(node => node.data('label'));
    const enrichmentsInNetwork = _.get(expressionTable, 'rows', []).filter(row => networkNodes.includes(row.geneName));

    const maxVal = _.max(enrichmentsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(enrichmentsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    const expressionHeader = _.get(expressionTable, 'header', []);
    const expressionRows = _.get(expressionTable, 'rows', []);

    const columns = [
      {
        Header: 'Gene Name',
        accessor: 'geneName'
      }
    ].concat(expressionHeader.map((className, index) => {
      return {
        Header: className,
        id: className,
        accessor: row => row.classValues[index]
      };
    }));
    return h('div.paint', [
      h('div.paint-content', [
        h('div', { className: classNames('paint-drawer', { 'closed': !state.drawerOpen }) }, [
          h('a', { onClick: e => this.toggleDrawer()}, [
            h(Icon, { icon: 'close'}),
          ]),
          h('div.paint-legend', [
            h('p', `low ${minVal}`),
            h('p', `high ${maxVal}`)
          ]),
          h('p', `columns correspond to the clockwise direction on the pie (first column starts at 12 O'Clock going clockwise)`),
          h(Table, {data: expressionRows, columns: columns} )
        ]),
        h(OmniBar, { name: state.name, datasource: state.datasource, onMenuClick: (e) => this.toggleDrawer() }),
        h(Network, { cy: state.cy, expressionTable: state.expressionTable })
      ])
    ]);
  }
}

module.exports = Paint;<|endoftext|>


<commit_before>const React = require('react');
const h = require('react-hyperscript');
const queryString = require('query-string');
const color = require('color');
const _ = require('lodash');
const classNames = require('classnames');

const cytoscape = require('cytoscape');
const cose = require('cytoscape-cose-bilkent');
cytoscape.use(cose);

const sbgn2Json = require('sbgnml-to-cytoscape');
const sbgnStylesheet = require('cytoscape-sbgn-stylesheet');

const Icon = require('../../common/components').Icon;
const PathwayCommonsService = require('../../services').PathwayCommonsService;

// const testData = require('./sample-enrichments');

const augmentEnrichmentData = require('./enrichment-model');

class Paint extends React.Component {
  constructor(props) {
    super(props);

    const cy = cytoscape({
      style: sbgnStylesheet(cytoscape),
      minZoom: 0.16,
      maxZoom: 4,
      headless: true
    });

    this.state = {
      enrichmentDataSets: [],
      enrichmentClasses: [],
      // enrichmentDataSets: testData.dataSetExpressionList,
      // enrichmentClasses: _.get(testData.dataSetClassList, '0.classes', []),
      enrichmentTable: {},
      cy: cy,
      name: '',
      datasource: '',
      drawerOpen: false
    };

    const query = queryString.parse(props.location.search);
    const enrichmentsURI = query.uri ? query.uri : null;

    if (enrichmentsURI != null) {
      fetch(enrichmentsURI)
        .then(response => response.json())
        .then(json => {
          // console.log(augmentEnrichmentData(json.dataSetClassList, json.dataSetExpressionList));
          this.setState({
            enrichmentClasses: _.get(json.dataSetClassList, '0.classes', []),
            enrichmentDataSets: json.dataSetExpressionList
          }, () => {
          });
        });
    }

    const expressions = _.get(this.state.dataSetExpressionList, '0.expressions', []);
    const searchParam = query.q ? query.q : 'S Phase';
    this.initPainter(expressions, searchParam);

  }

  componentWillUnmount() {
    this.state.cy.destroy();
  }

  percentToColour(val, min, max) {
    const distToMax = Math.abs(val - max);
    const distToMin = Math.abs(val - min);

    let hVal, lVal;
    if (distToMax > distToMin) {
      hVal = 240;
      lVal = ( val / Math.abs(max) ) * 100 + 20;
    } else {
      hVal = 0;
      lVal = ( val / Math.abs(max) ) * 100 - 20;
    }
    return color.hsl(hVal, 100, lVal).string();
  }

  colourMap(percent) {
    const hslValue = ( 1 - percent ) * 240;

    return color.hsl(hslValue, 100, 50).string();
  }

  // only call this after you know component is mounted
  initPainter(expressions, queryParam) {
    const state = this.state;
    const query = {
      q: queryParam,
      gt: 2,
      lt: 100,
      type: 'Pathway',
      datasource: 'reactome'
    };

    PathwayCommonsService.querySearch(query)
      .then(searchResults => {
        const uri = _.get(searchResults, '0.uri', null);
        if (uri != null) {
          PathwayCommonsService.query(uri, 'json', 'Named/displayName')
          .then(response => {
            this.setState({
              name: response ? response.traverseEntry[0].value.pop() : ''
            });
          });

        PathwayCommonsService.query(uri, 'json', 'Entity/dataSource/displayName')
          .then(responseObj => {
            this.setState({
              datasource: responseObj ? responseObj.traverseEntry[0].value.pop() : ''
            });
          });

        PathwayCommonsService.query(uri, 'SBGN')
          .then(text => {
            const sbgnJson = sbgn2Json(text);
            state.cy.remove('*');
            state.cy.add(sbgnJson);
            state.cy.layout({
              name: 'cose-bilkent',
              nodeDimensionsIncludeLabels: true
            }).run();

            const enrichmentTable = this.createEnrichmentTable();

            const networkNodes = state.cy.nodes().map(node => node.data('label'));
            const enrichmentsInNetwork = enrichmentTable.rows.filter(row => networkNodes.includes(row.geneName));
            const maxVal = _.max(enrichmentsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
            const minVal = _.min(enrichmentsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

            enrichmentsInNetwork.forEach(row => {
              // probably more efficient to add the enrichment data to the node field instead of interating twice
              state.cy.nodes().filter(node => node.data('label') === row.geneName).forEach(node => {

                const numSlices = row.classValues.length > 16 ? 16 : row.classValues.length;

                const pieStyle = {
                  'pie-size': '100%'
                };
                for (let i = 1; i <= numSlices; i++) {

                  pieStyle['pie-' + i + '-background-size'] = 100 / numSlices;
                  pieStyle['pie-' + i + '-background-opacity'] = 1;
                  pieStyle['pie-' + i + '-background-color'] = this.percentToColour(row.classValues[i-1] / maxVal);
                }

                node.style(pieStyle);
              });

              this.setState({enrichmentTable: enrichmentTable});

            });
          });
        }
      });
  }

  componentDidMount() {
    const props = this.props;
    const state = this.state;
    const container = document.getElementById('cy-container');
    state.cy.mount(container);
  }

  toggleDrawer() {
    this.setState({drawerOpen: !this.state.drawerOpen});
  }

  createEnrichmentTable() {
    const state = this.state;
    const enrichmentTable = {};

    const enrichmentClasses = state.enrichmentClasses;
    const header = _.uniq(state.enrichmentClasses);

    enrichmentTable.header = header;
    enrichmentTable.rows = _.get(state.enrichmentDataSets, '0.expressions', []).map(enrichment => {
      const geneName = enrichment.geneName;
      const values = enrichment.values;

      const class2ValuesMap = new Map();

      for (let enrichmentClass of header) {
        class2ValuesMap.set(enrichmentClass, []);
      }

      for (let i = 0; i < values.length; i++) {
        class2ValuesMap.get(enrichmentClasses[i]).push(values[i]);
      }

      return { geneName: geneName, classValues: Array.from(class2ValuesMap.entries()).map((entry =>  _.mean(entry[1]).toFixed(2))) };
    });

    return enrichmentTable;
  }

  render() {
    const state = this.state;

    const enrichmentTable = state.enrichmentTable;

    const networkNodes = state.cy.nodes().map(node => node.data('label'));
    const enrichmentsInNetwork = _.get(enrichmentTable, 'rows', []).filter(row => networkNodes.includes(row.geneName));

    const maxVal = _.max(enrichmentsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(enrichmentsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    const enrichmentTableHeader = [h('th', '')].concat(_.get(enrichmentTable, 'header', []).map(column => h('th', column)));
    const enrichmentTableRows = _.sortBy(
      _.get(enrichmentTable, 'rows', []), (o) => o.geneName
    ).map(row => h('tr',[h('td', row.geneName)].concat(row.classValues.map(cv => h('td', cv)))));

    return h('div.paint', [
      h('div.paint-content', [
        h('div', { className: classNames('paint-drawer', !state.drawerOpen ? 'closed' : '') }, [
          h('a', { onClick: e => this.toggleDrawer()}, [
            h(Icon, { icon: 'close'}),
          ]),
          h('div.paint-legend', [
            h('p', `low ${minVal}`),
            h('p', `high ${maxVal}`)
          ]),
          h('p', `columns correspond to the clockwise direction on the pie (first column starts at 12 O'Clock going clockwise)`),
          h('table', [
            h('thead', [
              h('tr', enrichmentTableHeader)
            ]),
            h('tbody', enrichmentTableRows)
          ])

        ]),
        h('div.paint-omnibar', [
          h('a', { onClick: e => this.toggleDrawer() }, [
            h(Icon, { icon: 'menu' }, 'click')
          ]),
          h('h5', `${state.name} | ${state.datasource}`)
        ]),
        h('div.paint-graph', [
          h(`div.#cy-container`, {style: {width: '100vw', height: '100vh'}})
        ])
      ])
    ]);
  }
}

module.exports = Paint;<commit_msg>Refactor paint into smaller components, use cdc to get the graph<commit_after>const React = require('react');
const h = require('react-hyperscript');
const queryString = require('query-string');
const color = require('color');
const _ = require('lodash');
const classNames = require('classnames');

const cytoscape = require('cytoscape');
const cose = require('cytoscape-cose-bilkent');
cytoscape.use(cose);

const sbgnStylesheet = require('cytoscape-sbgn-stylesheet');

const Icon = require('../../common/components').Icon;
const { CDC, PathwayCommonsService } = require('../../services');

class OmniBar extends React.Component {
  render() {
    const props = this.props;
    return h('div.paint-omnibar', [
      h('a', { onClick: e => props.onMenuClick(e) }, [
        h(Icon, { icon: 'menu' }, 'click')
      ]),
      h('h5', `${props.name} | ${props.datasource}`)
    ]);
  }
}

// props
// expression table
// cy
class Network extends React.Component {
  componentWillUnmount() {
    this.state.cy.destroy();
  }

  componentDidMount() {
    const props = this.props;
    const container = document.getElementById('cy-container');
    props.cy.mount(container);
  }

  colourMap(val, min, max) {
    const distToMax = Math.abs(val - max);
    const distToMin = Math.abs(val - min);

    let hVal, lVal;
    if (distToMax > distToMin) {
      hVal = 240;
      lVal = ( val / Math.abs(max) ) * 100 + 20;
    } else {
      hVal = 0;
      lVal = ( val / Math.abs(max) ) * 100 - 20;
    }
    return color.hsl(hVal, 100, lVal).string();
  }

  percentToColour(percent) {
    const hslValue = ( 1 - percent ) * 240;

    return color.hsl(hslValue, 100, 50).string();
  }

  applyExpressionData() {
    const props = this.props;
    const expressionTable = props.expressionTable;

    const networkNodes = _.uniq(props.cy.nodes('[class="macromolecule"]').map(node => node.data('label'))).sort();

    const expressionsInNetwork = expressionTable.rows.filter(row => networkNodes.includes(row.geneName));
    const maxVal = _.max(expressionsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(expressionsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    expressionsInNetwork.forEach(expression => {
      // probably more efficient to add the expression data to the node field instead of interating twice
      props.cy.nodes().filter(node => node.data('label') === expression.geneName).forEach(node => {

        const numSlices = expression.classValues.length > 16 ? 16 : expression.classValues.length;

        const pieStyle = {
          'pie-size': '100%'
        };
        for (let i = 1; i <= numSlices; i++) {

          pieStyle['pie-' + i + '-background-size'] = 100 / numSlices;
          pieStyle['pie-' + i + '-background-opacity'] = 1;
          pieStyle['pie-' + i + '-background-color'] = this.percentToColour(expression.classValues[i-1] / maxVal);
        }

        node.style(pieStyle);
      });
    });
  }

  render() {
    return h('div.paint-graph', [ h(`div.#cy-container`, {style: {width: '100vw', height: '100vh'}}) ]);
  }

}

class Paint extends React.Component {
  constructor(props) {
    super(props);

    const cy = cytoscape({
      style: sbgnStylesheet(cytoscape),
      minZoom: 0.16,
      maxZoom: 4,
      headless: true
    });

    this.state = {
      rawEnrichmentData: {},
      expressionTable: {},
      cy: cy,
      name: '',
      datasource: '',
      drawerOpen: false
    };

    const query = queryString.parse(props.location.search);
    const enrichmentsURI = query.uri ? query.uri : null;

    if (enrichmentsURI != null) {
      fetch(enrichmentsURI)
        .then(response => response.json())
        .then(json => {
          this.setState({rawEnrichmentData: json});

          const expressionClasses = _.get(json.dataSetClassList, '0.classes', []);
          const expressions = _.get(json.dataSetExpressionList, '0.expressions', []);
          const searchParam = query.q;
          this.initPainter(expressions, expressionClasses, searchParam);

        });
    }
  }

  componentWillUnmount() {
    this.state.cy.destroy();
  }

  // only call this after you know component is mounted
  initPainter(expressions, expressionClasses, queryParam) {
    const state = this.state;
    const query = {
      q: queryParam,
      gt: 2,
      lt: 100,
      type: 'Pathway',
      datasource: 'reactome'
    };

    const expressionTable = {};

    const header = _.uniq(expressionClasses);

    expressionTable.header = header;
    expressionTable.rows = expressions.map(enrichment => {
      const geneName = enrichment.geneName;
      const values = enrichment.values;

      const class2ValuesMap = new Map();

      for (let enrichmentClass of header) {
        class2ValuesMap.set(enrichmentClass, []);
      }

      for (let i = 0; i < values.length; i++) {
        class2ValuesMap.get(expressionClasses[i]).push(values[i]);
      }

      return { geneName: geneName, classValues: Array.from(class2ValuesMap.entries()).map((entry =>  _.mean(entry[1]).toFixed(2))) };
    });

    this.setState({
      expressionTable: expressionTable
    });

    PathwayCommonsService.querySearch(query, false)
      .then(searchResults => {
        const uri = _.get(searchResults, '0.uri', null);

        if (uri != null) {
          CDC.getGraphAndLayout(uri, 'latest').then(response => {
            this.setState({
              name: _.get(response, 'graph.pathwayMetadata.title', 'N/A'),
              datasource: _.get(response, 'graph.pathwayMetadata.dataSource.0', 'N/A'),
            });

            state.cy.remove('*');
            state.cy.add({
              nodes: _.get(response, 'graph.nodes', []),
              edges: _.get(response, 'graph.edges', [])
            });

            state.cy.layout({
              name: 'cose-bilkent',
              nodeDimensionsIncludeLabels: true
            }).run();

          });
        }
    });
  }

  toggleDrawer() {
    this.setState({drawerOpen: !this.state.drawerOpen});
  }

  render() {
    const state = this.state;

    const expressionTable = state.expressionTable;

    const networkNodes = state.cy.nodes().map(node => node.data('label'));
    const enrichmentsInNetwork = _.get(expressionTable, 'rows', []).filter(row => networkNodes.includes(row.geneName));

    const maxVal = _.max(enrichmentsInNetwork.map(row => _.max(row.classValues)).map((k, v) => parseFloat(k)));
    const minVal = _.min(enrichmentsInNetwork.map(row => _.min(row.classValues)).map((k, v) => parseFloat(k)));

    const expressionTableHeader = [h('th', '')].concat(_.get(expressionTable, 'header', []).map(column => h('th', column)));
    const expressionTableBody = _.sortBy(
      _.get(expressionTable, 'rows', []), (o) => o.geneName
    ).map(row => h('tr',[h('td', row.geneName)].concat(row.classValues.map(cv => h('td', cv)))));

    return h('div.paint', [
      h('div.paint-content', [
        h('div', { className: classNames('paint-drawer', !state.drawerOpen ? 'closed' : '') }, [
          h('a', { onClick: e => this.toggleDrawer()}, [
            h(Icon, { icon: 'close'}),
          ]),
          h('div.paint-legend', [
            h('p', `low ${minVal}`),
            h('p', `high ${maxVal}`)
          ]),
          h('p', `columns correspond to the clockwise direction on the pie (first column starts at 12 O'Clock going clockwise)`),
          h('table', [
            h('thead', [
              h('tr', expressionTableHeader)
            ]),
            h('tbody', expressionTableBody)
          ])

        ]),
        h(OmniBar, { name: state.name, datasource: state.datasource, onMenuClick: (e) => this.toggleDrawer() }),
        h(Network, { cy: state.cy, expressionTable: state.expressionTable })
      ])
    ]);
  }
}

module.exports = Paint;<|endoftext|>


<commit_before>const React = require('react');
const h = require('react-hyperscript');
const queryString = require('query-string');
const color = require('color');
const _ = require('lodash');
const classNames = require('classnames');

const cytoscape = require('cytoscape');
const cose = require('cytoscape-cose-bilkent');
cytoscape.use(cose);

const sbgn2Json = require('sbgnml-to-cytoscape');
const sbgnStylesheet = require('cytoscape-sbgn-stylesheet');

const Icon = require('../../common/components').Icon;
const PathwayCommonsService = require('../../services').PathwayCommonsService;

class Paint extends React.Component {
  constructor(props) {
    super(props);

    const cy = cytoscape({
      style: sbgnStylesheet(cytoscape),
      minZoom: 0.16,
      maxZoom: 4,
      headless: true
    });

    this.state = {
      enrichmentDataSets: [],
      enrichmentClasses: [],
      enrichmentTable: {},
      cy: cy,
      name: '',
      datasource: '',
      drawerOpen: false
    };

  }

  componentWillUnmount() {
    this.state.cy.destroy();
  }

  colorMap(value) {
    return color.hsl(value * 240 / 255, 100, 50).string();
  }

  percentToColour(percent, colourRangeStart, colourRangeEnd) {
    const hslValue = ( percent  * ( colourRangeEnd - colourRangeStart ) ) + colourRangeStart;

    return color.hsl(hslValue, 100, 50).string();
  }

  // only call this after you know component is mounted
  initPainter(expressions, queryParam) {
    const state = this.state;
    const query = {
      q: queryParam,
      gt: 2,
      lt: 100,
      type: 'Pathway',
      datasource: 'reactome'
    };

    PathwayCommonsService.querySearch(query)
      .then(searchResults => {
        const uri = _.get(searchResults, '0.uri', null);
        if (uri != null) {
          PathwayCommonsService.query(uri, 'json', 'Named/displayName')
          .then(response => {
            this.setState({
              name: response ? response.traverseEntry[0].value.pop() : ''
            });
          });

        PathwayCommonsService.query(uri, 'json', 'Entity/dataSource/displayName')
          .then(responseObj => {
            this.setState({
              datasource: responseObj ? responseObj.traverseEntry[0].value.pop() : ''
            });
          });

        PathwayCommonsService.query(uri, 'SBGN')
          .then(text => {
            const sbgnJson = sbgn2Json(text);
            state.cy.remove('*');
            state.cy.add(sbgnJson);
            state.cy.layout({
              name: 'cose-bilkent',
              nodeDimensionsIncludeLabels: true
            }).run();

            const enrichmentTable = this.createEnrichmentTable();

            enrichmentTable.rows.forEach(row => {
              state.cy.nodes().filter(node => node.data('label') === row.geneName).forEach(node => {
                const maxVal = _.max(row.classValues);
                const minVal = _.min(row.classValues);

                const numSlices = row.classValues.length > 16 ? 16 : row.classValues.length;

                const pieStyle = {
                  'pie-size': '100%'
                };
                for (let i = 1; i <= numSlices; i++) {

                  pieStyle['pie-' + i + '-background-size'] = 100 / numSlices;
                  pieStyle['pie-' + i + '-background-opacity'] = 1;
                  pieStyle['pie-' + i + '-background-color'] = this.percentToColour(row.classValues[i-1] / maxVal, 0, 240);
                }

                node.style(pieStyle);
              });

              this.setState({enrichmentTable: enrichmentTable});

            });
            // expressions.forEach(expression => {
            //   state.cy.nodes().filter(node => node.data('label') === expression.geneName).forEach(node => {
            //     const maxVal = _.max(expression.values);
            //     const minVal = _.min(expression.values);

            //     node.style({
            //       'pie-size': '100%',
            //       'pie-1-background-color': this.percentToColour(expression.values[0] / maxVal, 0, 240),
            //       'pie-1-background-size': '50%',
            //       'pie-1-background-opacity': 1,
            //       'pie-2-background-color': this.percentToColour(expression.values[expression.values.length - 1] / maxVal, 0, 240),
            //       'pie-2-background-size': '50%',
            //       'pie-2-background-opacity': 1
            //     });
            //   });
            // });

          });
        }
      });
  }

  componentDidMount() {
    const props = this.props;
    const state = this.state;
    const container = document.getElementById('cy-container');
    state.cy.mount(container);

    const query = queryString.parse(props.location.search);
    const enrichmentsURI = query.uri ? query.uri : null;

    if (enrichmentsURI != null) {
      fetch(enrichmentsURI)
        .then(response => response.json())
        .then(json => {
          this.setState({
            enrichmentClasses: _.get(json.dataSetClassList, '0.classes', []),
            enrichmentDataSets: json.dataSetExpressionList
          }, () => {
            const expressions = _.get(json.dataSetExpressionList, '0.expressions', []);
            const searchParam = query.q ? query.q : '';
            this.initPainter(expressions, searchParam);
          });
        });
    }
  }

  toggleDrawer() {
    this.setState({drawerOpen: !this.state.drawerOpen});
  }

  createEnrichmentTable() {
    const state = this.state;
    const enrichmentTable = {};

    const enrichmentClasses = state.enrichmentClasses;
    const header = _.uniq(state.enrichmentClasses);

    enrichmentTable.header = header;
    enrichmentTable.rows = _.get(state.enrichmentDataSets, '0.expressions', []).map(enrichment => {
      const geneName = enrichment.geneName;
      const values = enrichment.values;

      const class2ValuesMap = new Map();

      for (let enrichmentClass of header) {
        class2ValuesMap.set(enrichmentClass, []);
      }

      for (let i = 0; i < values.length; i++) {
        class2ValuesMap.get(enrichmentClasses[i]).push(values[i]);
      }

      return { geneName: geneName, classValues: Array.from(class2ValuesMap.entries()).map((entry =>  _.mean(entry[1]).toFixed(2))) };
    });

    return enrichmentTable;
  }

  render() {
    const state = this.state;

    const enrichmentTable = state.enrichmentTable;
    const enrichmentTableHeader = _.get(enrichmentTable, 'header', []).map(column => h('div', column));
    const enrichmentTableRows = _.get(enrichmentTable, 'rows', []).map(row => h('div', `Gene: ${row.geneName}, ${JSON.stringify(row.classValues, null, 2)}`));

    const enrichmentClassesData = Object.entries(_.countBy(state.enrichmentClasses))
      .map(entry => {
        return h('p', `class: ${entry[0]}, number of samples: ${entry[1]}`);
      });

    return h('div.paint', [
      h('div.paint-content', [
        h('div', { className: classNames('paint-drawer', !state.drawerOpen ? 'closed' : '') }, [
          h('a', { onClick: e => this.toggleDrawer()}, [
            h(Icon, { icon: 'close'})
          ]),
        ].concat(enrichmentClassesData).concat(enrichmentTableHeader).concat(enrichmentTableRows)),
        h('div.paint-omnibar', [
          h('a', { onClick: e => this.toggleDrawer() }, [
            h(Icon, { icon: 'menu' }, 'click')
          ]),
          h('h5', `${state.name} | ${state.datasource}`)
        ]),
        h('div.paint-graph', [
          h(`div.#cy-container`, {style: {width: '100vw', height: '100vh'}})
        ])
      ])
    ]);
  }
}

module.exports = Paint;<commit_msg>Set default paint data for demo purposes<commit_after>const React = require('react');
const h = require('react-hyperscript');
const queryString = require('query-string');
const color = require('color');
const _ = require('lodash');
const classNames = require('classnames');

const cytoscape = require('cytoscape');
const cose = require('cytoscape-cose-bilkent');
cytoscape.use(cose);

const sbgn2Json = require('sbgnml-to-cytoscape');
const sbgnStylesheet = require('cytoscape-sbgn-stylesheet');

const Icon = require('../../common/components').Icon;
const PathwayCommonsService = require('../../services').PathwayCommonsService;

const testData = require('./sample-enrichments');

class Paint extends React.Component {
  constructor(props) {
    super(props);

    const cy = cytoscape({
      style: sbgnStylesheet(cytoscape),
      minZoom: 0.16,
      maxZoom: 4,
      headless: true
    });

    this.state = {
      enrichmentDataSets: testData.dataSetExpressionList,
      enrichmentClasses: _.get(testData.dataSetClassList, '0.classes', []),
      enrichmentTable: {},
      cy: cy,
      name: '',
      datasource: '',
      drawerOpen: false
    };

    const query = queryString.parse(props.location.search);
    const enrichmentsURI = query.uri ? query.uri : null;

    if (enrichmentsURI != null) {
      fetch(enrichmentsURI)
        .then(response => response.json())
        .then(json => {
          this.setState({
            enrichmentClasses: _.get(json.dataSetClassList, '0.classes', []),
            enrichmentDataSets: json.dataSetExpressionList
          }, () => {
          });
        });
    }

    const expressions = _.get(this.state.dataSetExpressionList, '0.expressions', []);
    const searchParam = query.q ? query.q : 'S Phase';
    this.initPainter(expressions, searchParam);

  }

  componentWillUnmount() {
    this.state.cy.destroy();
  }

  colorMap(value) {
    return color.hsl(value * 240 / 255, 100, 50).string();
  }

  percentToColour(percent, colourRangeStart, colourRangeEnd) {
    const hslValue = ( percent  * ( colourRangeEnd - colourRangeStart ) ) + colourRangeStart;

    return color.hsl(hslValue, 100, 50).string();
  }

  // only call this after you know component is mounted
  initPainter(expressions, queryParam) {
    const state = this.state;
    const query = {
      q: queryParam,
      gt: 2,
      lt: 100,
      type: 'Pathway',
      datasource: 'reactome'
    };

    PathwayCommonsService.querySearch(query)
      .then(searchResults => {
        const uri = _.get(searchResults, '0.uri', null);
        if (uri != null) {
          PathwayCommonsService.query(uri, 'json', 'Named/displayName')
          .then(response => {
            this.setState({
              name: response ? response.traverseEntry[0].value.pop() : ''
            });
          });

        PathwayCommonsService.query(uri, 'json', 'Entity/dataSource/displayName')
          .then(responseObj => {
            this.setState({
              datasource: responseObj ? responseObj.traverseEntry[0].value.pop() : ''
            });
          });

        PathwayCommonsService.query(uri, 'SBGN')
          .then(text => {
            const sbgnJson = sbgn2Json(text);
            state.cy.remove('*');
            state.cy.add(sbgnJson);
            state.cy.layout({
              name: 'cose-bilkent',
              nodeDimensionsIncludeLabels: true
            }).run();

            const enrichmentTable = this.createEnrichmentTable();

            enrichmentTable.rows.forEach(row => {
              state.cy.nodes().filter(node => node.data('label') === row.geneName).forEach(node => {
                const maxVal = _.max(row.classValues);
                const minVal = _.min(row.classValues);

                const numSlices = row.classValues.length > 16 ? 16 : row.classValues.length;

                const pieStyle = {
                  'pie-size': '100%'
                };
                for (let i = 1; i <= numSlices; i++) {

                  pieStyle['pie-' + i + '-background-size'] = 100 / numSlices;
                  pieStyle['pie-' + i + '-background-opacity'] = 1;
                  pieStyle['pie-' + i + '-background-color'] = this.percentToColour(row.classValues[i-1] / maxVal, 0, 240);
                }

                node.style(pieStyle);
              });

              this.setState({enrichmentTable: enrichmentTable});

            });
          });
        }
      });
  }

  componentDidMount() {
    const props = this.props;
    const state = this.state;
    const container = document.getElementById('cy-container');
    state.cy.mount(container);
  }

  toggleDrawer() {
    this.setState({drawerOpen: !this.state.drawerOpen});
  }

  createEnrichmentTable() {
    const state = this.state;
    const enrichmentTable = {};

    const enrichmentClasses = state.enrichmentClasses;
    const header = _.uniq(state.enrichmentClasses);

    enrichmentTable.header = header;
    enrichmentTable.rows = _.get(state.enrichmentDataSets, '0.expressions', []).map(enrichment => {
      const geneName = enrichment.geneName;
      const values = enrichment.values;

      const class2ValuesMap = new Map();

      for (let enrichmentClass of header) {
        class2ValuesMap.set(enrichmentClass, []);
      }

      for (let i = 0; i < values.length; i++) {
        class2ValuesMap.get(enrichmentClasses[i]).push(values[i]);
      }

      return { geneName: geneName, classValues: Array.from(class2ValuesMap.entries()).map((entry =>  _.mean(entry[1]).toFixed(2))) };
    });

    return enrichmentTable;
  }

  render() {
    const state = this.state;

    const enrichmentTable = state.enrichmentTable;
    const enrichmentTableHeader = _.get(enrichmentTable, 'header', []).map(column => h('div', column));
    const enrichmentTableRows = _.get(enrichmentTable, 'rows', []).map(row => h('div', `Gene: ${row.geneName}, ${JSON.stringify(row.classValues, null, 2)}`));

    const enrichmentClassesData = Object.entries(_.countBy(state.enrichmentClasses))
      .map(entry => {
        return h('p', `class: ${entry[0]}, number of samples: ${entry[1]}`);
      });

    return h('div.paint', [
      h('div.paint-content', [
        h('div', { className: classNames('paint-drawer', !state.drawerOpen ? 'closed' : '') }, [
          h('a', { onClick: e => this.toggleDrawer()}, [
            h(Icon, { icon: 'close'})
          ]),
        ].concat(enrichmentClassesData).concat(enrichmentTableHeader).concat(enrichmentTableRows)),
        h('div.paint-omnibar', [
          h('a', { onClick: e => this.toggleDrawer() }, [
            h(Icon, { icon: 'menu' }, 'click')
          ]),
          h('h5', `${state.name} | ${state.datasource}`)
        ]),
        h('div.paint-graph', [
          h(`div.#cy-container`, {style: {width: '100vw', height: '100vh'}})
        ])
      ])
    ]);
  }
}

module.exports = Paint;<|endoftext|>


<commit_before>// Copyright (c) 2005 Tomoaki Kinoshita (http://script.spinelz.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

var DatePicker = Class.create();
DatePicker.className = {
  container: 'datepicker',
  header: 'header',
  preYears: 'pre_years',
  nextYears: 'next_years',
  years: 'years',
  mark: 'mark',
  markHover: 'markHover',
  calendar: 'calendar',
  date: 'date',
  dateHover: 'dateHover',
  holiday: 'holiday'
}
DatePicker.mark = {
  preYear: '<<',
  preMonth: '<',
  nextYear: '>>',
  nextMonth: '>'
}
DatePicker.week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
DatePicker.month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
DatePicker.daysOfMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
DatePicker.prototype = {

  initialize: function(element, target, trigger) {
    this.element = $(element);
    this.target = $(target);
    this.date = new Date();

    var options = Object.extend({
      month: this.date.getMonth() + 1,
      date: this.date.getDate(),
      year: this.date.getFullYear(),
      format: DateFormat.toLocaleDateString
    }, arguments[3] || {});

    this.format = options.format;

    this.date.setMonth(options.month - 1);
    this.date.setDate(options.date);
    this.date.setFullYear(options.year);

    this.calendar = this.build();
    this.element.appendChild(this.calendar);

    this.doclistener = this.hide.bindAsEventListener(this);
    Event.observe($(trigger), "click", this.show.bindAsEventListener(this));
    this.hide();
  },

  build: function() {
    var node =
      Builder.node(
        'DIV',
        {className: DatePicker.className.container},
        [
          this.buildHeader(),
          this.buildCalendar()
        ]);

    return node;
  },

  buildHeader: function() {
    var headerNodes = new Array();

    var nodes = this.multiBuild(
                  'DIV',
                  [DatePicker.mark.preYear, DatePicker.mark.preMonth],
                  DatePicker.className.mark,
                  DatePicker.className.markHover,
                  this.changeCalendar());
    headerNodes.push(Builder.node('DIV', {className: DatePicker.className.preYears}, nodes));

    nodes = this.multiBuild('SPAN', [this.getMonth(), this.date.getFullYear()]);
    headerNodes.push(Builder.node('DIV', {className: DatePicker.className.years}, nodes));

    nodes = this.multiBuild(
              'DIV',
              [DatePicker.mark.nextMonth, DatePicker.mark.nextYear],
              DatePicker.className.mark,
              DatePicker.className.markHover,
              this.changeCalendar());
    headerNodes.push(Builder.node('DIV', {className: DatePicker.className.nextYears}, nodes));

    return Builder.node('DIV', {className: DatePicker.className.header}, headerNodes);
  },

  multiBuild: function(tagType, params, className, hoverClass, clickEvent) {
    var children = new Array();
    for (var i = 0; i < params.length; i++) {
      var node;

      if (className)
        node = Builder.node(tagType, {className: className}, [params[i]]);
      else
        node = Builder.node(tagType, [params[i]]);

      if (hoverClass)
        this.setHoverEvent(node, className, hoverClass);

      if (clickEvent)
        Event.observe(node, "click", clickEvent.bindAsEventListener(this));

      children.push(node);
    }

    return children;
  },

  buildCalendar: function() {
    var table = Builder.node('TABLE', [
                  Builder.node(
                    'TBODY',
                    [
                      this.buildTableHeader(),
                      this.buildTableData()
                    ])
                ]);

    return Builder.node('DIV', {className: DatePicker.className.calendar}, [table]);
  },

  buildTableHeader: function() {
    var weekArray = new Array();
    for (var i = 0; i < DatePicker.week.length; i++) {
      weekArray.push(Builder.node('TH', DatePicker.week[i]));
    }

    return Builder.node('TR', weekArray);
  },

  buildTableData: function() {
    var length = DatePicker.week.length * 5;
    var firstDay = this.getFirstDay();
    var lastDate = this.getLastDate();
    var trs = new Array();
    var tds = new Array();

    for (var i = 0, day = 1; i <= length; i++) {
      if ((i < firstDay) || day > lastDate) {
        tds.push(Builder.node('TD'));

      } else {
        var className;
        if ((i % 7 == 0) || ((i+1) % 7 == 0))
          className = DatePicker.className.holiday;
        else
          className = DatePicker.className.date;

        node = Builder.node('TD', {className: className}, [day]);
        this.setHoverEvent(node, className, DatePicker.className.dateHover);
        Event.observe(node, "click", this.selectDate.bindAsEventListener(this));
        tds.push(node);
        day++;
      }

      if ((i + 1) % 7 == 0) {
        trs.push(Builder.node('TR', tds));
        tds = new Array();
      }
    }

    return trs;
  },

  refresh: function() {
    Element.remove(this.calendar);
    this.calendar = this.build();
    this.element.appendChild(this.calendar);
  },

  getLastDate: function() {
    var days = DatePicker.daysOfMonth[this.date.getMonth()];
    if ((this.date.getMonth() != 2) || !this.isLeapYear())
      return days;
    return days + 1;
  },

  isLeapYear: function() {
    var year = this.date.getFullYear();
    if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
      return true;
    return false;
  },

  getFirstDay: function() {
    var date = new Date(this.getMonth() + ' 1, ' + this.date.getFullYear());
    return date.getDay();
  },

  getMonth: function() {
    return  DatePicker.month[this.date.getMonth()];
  },

  setHoverEvent: function(element, normal, hover) {
    Event.observe(element, "mouseout", this.toggleHover(normal).bindAsEventListener(this));
    Event.observe(element, "mouseover", this.toggleHover(hover).bindAsEventListener(this));
  },

  toggleHover: function(className) {
    return function(event) {
      var src = Event.element(event);
      src.className = className;
    }
  },

  changeCalendar: function() {
    return function(event) {
      var value = this.getTextValue(Event.element(event)).nodeValue;

      switch (value) {
        case DatePicker.mark.preYear:
          this.date.setFullYear(this.date.getFullYear() - 1);
          break;

        case DatePicker.mark.preMonth:
          var pre = this.date.getMonth() - 1;
          if (pre < 0) {
            pre = 11;
            this.date.setFullYear(this.date.getFullYear() - 1);
          }
          this.date.setMonth(pre);
          break;

        case DatePicker.mark.nextYear:
          this.date.setFullYear(this.date.getFullYear() + 1);
          break;

        case DatePicker.mark.nextMonth:
          var next = this.date.getMonth() + 1;
          if (next > 11) {
            next = 0;
            this.date.setFullYear(this.date.getFullYear() + 1);
          }
          this.date.setMonth(next);
          break;
      }

      this.refresh();
    }
  },

  selectDate: function(event) {
    var src = Event.element(event);
    var text = this.getTextValue(src);

    this.date.setDate(text.nodeValue);

    var value = this.format(this.date);

    this.target.value = value;
    this.hide();
    src.className = DatePicker.className.date;
  },

  getTextValue: function(element) {
    return $A(element.childNodes).detect(function(child) {
      return (child.nodeType == 3);
    });
  },

  show: function(event) {
    Element.show(this.calendar);
    Event.observe(document, "click", this.doclistener);
    Event.stop(event);
  },

  hide: function() {
    Event.stopObserving(document, "click", this.doclistener);
    Element.hide(this.calendar);
  }
}

var DateFormat = {

  toDateString: function(date) {
    return date.toDateString();
  },

  toLocaleDateString: function(date) {
    return date.toLocaleDateString();
  },

  simpleFormat: function(formatStr) {
    return function(date) {
      var formated = formatStr.replace(/M+/g, date.getMonth() + 1);
      formated = formated.replace(/d+/g, date.getDate());
      formated = formated.replace(/y{4}/g, date.getFullYear());
      formated = formated.replace(/y{1,3}/g, new String(date.getFullYear()).substr(2));
      formated = formated.replace(/E+/g, DatePicker.week[date.getDay()]);

      return formated;
    }
  }
}
<commit_msg>upgrading datepicker.js to latest version from spinelz<commit_after>// Copyright (c) 2005 Tomoaki Kinoshita (http://script.spinelz.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

var DatePicker = Class.create();
DatePicker.className = {
  container: 'datepicker',
  header: 'datepicker_header',
  preYears: 'datepicker_preYears',
  nextYears: 'datepicker_nextYears',
  years: 'datepicker_years',
  mark: 'datepicker_mark',
  calendar: 'datepicker_calendar',
  date: 'datepicker_date',
  holiday: 'datepicker_holiday',
  ym: 'datepicker_ym',
  table: 'datepicker_table',
  tableTh: 'datepicker_tableTh'
}
DatePicker.mark = {
  preYear: '<<',
  preMonth: '<',
  nextYear: '>>',
  nextMonth: '>'
}
DatePicker.prototype = {

  initialize: function(element, target, trigger) {
    this.element = $(element);
    Element.setStyle(this.element, {visibility: 'hidden'});
    this.target = $(target);
    this.date = new Date();

    var options = Object.extend({
      month: this.date.getMonth() + 1,
      date: this.date.getDate(),
      year: this.date.getFullYear(),
      format: DateUtil.toLocaleDateString,
      cssPrefix: 'custom_',
      callBack: Prototype.emptyFunction
    }, arguments[3] || {});

    var customCss = CssUtil.appendPrefix(options.cssPrefix, DatePicker.className);
    this.classNames = new CssUtil([DatePicker.className, customCss]);

    this.format = options.format;
    this.callBack = options.callBack;

    this.date.setMonth(options.month - 1);
    this.date.setDate(options.date);
    this.date.setFullYear(options.year);

    this.calendar = this.build();
    this.element.appendChild(this.calendar);

    this.doclistener = this.hide.bindAsEventListener(this);
    Event.observe($(trigger), "click", this.show.bindAsEventListener(this));
    this.hide();
    Element.setStyle(this.element, {visibility: 'visible'});
  },

  build: function() {
    var node =
      Builder.node(
        'DIV',
        {className: this.classNames.joinClassNames('container')},
        [
          this.buildHeader(),
          this.buildCalendar()
        ]);

    return node;
  },

  buildHeader: function() {
    var headerNodes = new Array();

    var nodes = this.multiBuild(
                  'DIV',
                  [DatePicker.mark.preYear, DatePicker.mark.preMonth],
                  'mark',
                  true,
                  this.changeCalendar());
    var className = this.classNames.joinClassNames('preYears');
    headerNodes.push(Builder.node('DIV', {className: className}, nodes));

    nodes = this.multiBuild('SPAN', [this.getMonth(), this.date.getFullYear()], 'ym');
    className = this.classNames.joinClassNames('years');
    headerNodes.push(Builder.node('DIV', {className: className}, nodes));

    nodes = this.multiBuild(
              'DIV',
              [DatePicker.mark.nextMonth, DatePicker.mark.nextYear],
              'mark',
              true,
              this.changeCalendar());
    className = this.classNames.joinClassNames('nextYears');
    headerNodes.push(Builder.node('DIV', {className: className}, nodes));

    className = this.classNames.joinClassNames('header');
    return Builder.node('DIV', {className: className}, headerNodes);
  },

  multiBuild: function(tagType, params, className, hover, clickEvent) {
    var children = [];
    for (var i = 0; i < params.length; i++) {
      var node;

      node = Builder.node(tagType, [params[i]]);
      if (className)
        this.classNames.addClassNames(node, className);

      if (hover)
        new Hover(node);

      if (clickEvent)
        Event.observe(node, "click", clickEvent.bindAsEventListener(this));

      children.push(node);
    }

    return children;
  },

  buildCalendar: function() {
    var className = this.classNames.joinClassNames('table');
    var node = Builder.node('TBODY', [this.buildTableHeader(), this.buildTableData()]);
    var table = Builder.node('TABLE', {className: className}, [node]);

    className = this.classNames.joinClassNames('calendar');
    return Builder.node('DIV', {className: className}, [table]);
  },

  buildTableHeader: function() {
    var weekArray = new Array();
    var className = this.classNames.joinClassNames('tableTh');
    for (var i = 0; i < DateUtil.dayOfWeek.length; i++) {
      weekArray.push(
        Builder.node('TH', {className: className}, [DateUtil.dayOfWeek[i]]));
    }

    return Builder.node('TR', weekArray);
  },

  buildTableData: function() {
    var length = DateUtil.dayOfWeek.length * 5;
    var year = this.date.getFullYear();
    var month = this.date.getMonth();
    var firstDay = DateUtil.getFirstDate(year, month).getDay();
    var lastDate = DateUtil.getLastDate(year, month).getDate();
    var trs = new Array();
    var tds = new Array();

    for (var i = 0, day = 1; i <= length; i++) {
      if ((i < firstDay) || day > lastDate) {
        tds.push(Builder.node('TD'));

      } else {
        var className;
        if ((i % 7 == 0) || ((i+1) % 7 == 0))
          className = 'holiday';
        else
          className = 'date';

        var defaultClass = this.classNames.joinClassNames(className);
        node = Builder.node('TD', {className: defaultClass}, [day]);
        new Hover(node);
        Event.observe(node, "click", this.selectDate.bindAsEventListener(this));
        tds.push(node);
        day++;
      }

      if ((i + 1) % 7 == 0) {
        trs.push(Builder.node('TR', tds));
        tds = new Array();
      }
    }

    return trs;
  },

  refresh: function() {
    Element.remove(this.calendar);
    this.calendar = this.build();
    this.element.appendChild(this.calendar);
  },

  getMonth: function() {
    return  DateUtil.months[this.date.getMonth()];
  },

  changeCalendar: function() {
    return function(event) {
      var value = Element.getTextNodes(Event.element(event))[0].nodeValue;

      switch (value) {
        case DatePicker.mark.preYear:
          this.date.setFullYear(this.date.getFullYear() - 1);
          break;

        case DatePicker.mark.preMonth:
          var pre = this.date.getMonth() - 1;
          if (pre < 0) {
            pre = 11;
            this.date.setFullYear(this.date.getFullYear() - 1);
          }
          this.date.setMonth(pre);
          break;

        case DatePicker.mark.nextYear:
          this.date.setFullYear(this.date.getFullYear() + 1);
          break;

        case DatePicker.mark.nextMonth:
          var next = this.date.getMonth() + 1;
          if (next > 11) {
            next = 0;
            this.date.setFullYear(this.date.getFullYear() + 1);
          }
          this.date.setMonth(next);
          break;
      }

      this.refresh();
    }
  },

  selectDate: function(event) {
    var src = Event.element(event);
    var text = Element.getTextNodes(src)[0];

    this.date.setDate(text.nodeValue);

    var value = this.format(this.date);

    if (this.target.value || this.target.value == '') {
      this.target.value = value;
    } else {
      this.target.innerHTML = value;
    }

    this.hide();
    this.classNames.refreshClassNames(src, 'date');

    this.callBack(this);
  },

  show: function(event) {
    Element.show(this.calendar);
    Event.observe(document, "click", this.doclistener);
    if (event) {
        Event.stop(event);
    }
  },

  hide: function() {
    Event.stopObserving(document, "click", this.doclistener);
    Element.hide(this.calendar);
  },

  addTrigger: function(trigger) {
    Event.observe($(trigger), 'click', this.show.bindAsEventListener(this));
  },

  changeTarget: function(target) {
    this.target = $(target);
  }
}
<|endoftext|>


<commit_before>// @flow
export const UI_OPEN_SIDEBAR = 'UI_OPEN_SIDEBAR'
export const UI_CLOSE_SIDEBAR = 'UI_CLOSE_SIDEBAR'
export const UI_WINDOW_RESIZE = 'UI_WINDOW_RESIZE'

export const CLOSE_SIDEBAR = () => ({
	type: UI_CLOSE_SIDEBAR
})

export const OPEN_SIDEBAR = () => ({
	type: UI_OPEN_SIDEBAR
})

export const WINDOW_RESIZE = (innerWidth: number) => ({
	type: UI_WINDOW_RESIZE,
	payload: {
		innerWidth
	}
})

export type UI_OPEN_SIDEBAR_TYPE = {type: 'UI_OPEN_SIDEBAR'}
export type UI_CLOSE_SIDEBAR_TYPE = {type: 'UI_CLOSE_SIDEBAR'}
export type UI_WINDOW_RESIZE_TYPE = {
	type: 'UI_WINDOW_RESIZE',
	payload: {innerWidth: number}
}
<commit_msg>feat(actions): layout - OPEN/CLOSE_SIDEBAR -> TOGGLE_SIDEBAR<commit_after>// @flow
export const UI_TOGGLE_SIDEBAR = 'UI_TOGGLE_SIDEBAR'
export const UI_WINDOW_RESIZE = 'UI_WINDOW_RESIZE'

export const TOGGLE_SIDEBAR = () => ({
	type: UI_TOGGLE_SIDEBAR
})

export const WINDOW_RESIZE = (innerWidth: number) => ({
	type: UI_WINDOW_RESIZE,
	payload: {
		innerWidth
	}
})

export type UI_TOGGLE_SIDEBAR_TYPE = {type: 'UI_TOGGLE_SIDEBAR'}
export type UI_WINDOW_RESIZE_TYPE = {
	type: 'UI_WINDOW_RESIZE',
	payload: {innerWidth: number}
}
<|endoftext|>


<commit_before>define(function (require) {

    var zrUtil = require('zrender/core/util');
    var symbolCreator = require('../../util/symbol');
    var graphic = require('../../util/graphic');
    var listComponentHelper = require('../helper/listComponent');

    var curry = zrUtil.curry;

    var LEGEND_DISABLE_COLOR = '#ccc';

    function dispatchSelectAction(name, api) {
        api.dispatchAction({
            type: 'legendToggleSelect',
            name: name
        });
    }

    function dispatchHighlightAction(seriesModel, dataName, api) {
        seriesModel.get('legendHoverLink') && api.dispatchAction({
            type: 'highlight',
            seriesName: seriesModel.name,
            name: dataName
        });
    }

    function dispatchDownplayAction(seriesModel, dataName, api) {
        seriesModel.get('legendHoverLink') && api.dispatchAction({
            type: 'downplay',
            seriesName: seriesModel.name,
            name: dataName
        });
    }

    return require('../../echarts').extendComponentView({

        type: 'legend',

        init: function () {
            this._symbolTypeStore = {};
        },

        render: function (legendModel, ecModel, api) {
            var group = this.group;
            group.removeAll();

            if (!legendModel.get('show')) {
                return;
            }

            var selectMode = legendModel.get('selectedMode');
            var itemAlign = legendModel.get('align');

            if (itemAlign === 'auto') {
                itemAlign = (legendModel.get('left') === 'right'
                    && legendModel.get('orient') === 'vertical')
                    ? 'right' : 'left';
            }

            var legendItemMap = {};
            var legendDrawedMap = {};
            zrUtil.each(legendModel.getData(), function (itemModel) {
                var seriesName = itemModel.get('name');
                // Use empty string or \n as a newline string
                if (seriesName === '' || seriesName === '\n') {
                    group.add(new graphic.Group({
                        newline: true
                    }));
                }

                var seriesModel = ecModel.getSeriesByName(seriesName)[0];

                legendItemMap[seriesName] = itemModel;

                if (!seriesModel || legendDrawedMap[seriesName]) {
                    // Series not exists
                    return;
                }

                var data = seriesModel.getData();
                var color = data.getVisual('color');

                // If color is a callback function
                if (typeof color === 'function') {
                    // Use the first data
                    color = color(seriesModel.getDataParams(0));
                }

                // Using rect symbol defaultly
                var legendSymbolType = data.getVisual('legendSymbol') || 'roundRect';
                var symbolType = data.getVisual('symbol');

                var itemGroup = this._createItem(
                    seriesName, itemModel, legendModel,
                    legendSymbolType, symbolType,
                    itemAlign, color,
                    selectMode
                );

                itemGroup.on('click', curry(dispatchSelectAction, seriesName, api))
                    .on('mouseover', curry(dispatchHighlightAction, seriesModel, '', api))
                    .on('mouseout', curry(dispatchDownplayAction, seriesModel, '', api));

                legendDrawedMap[seriesName] = true;
            }, this);

            ecModel.eachRawSeries(function (seriesModel) {
                if (seriesModel.legendDataProvider) {
                    var data = seriesModel.legendDataProvider();
                    data.each(function (idx) {
                        var name = data.getName(idx);

                        // Avoid mutiple series use the same data name
                        if (!legendItemMap[name] || legendDrawedMap[name]) {
                            return;
                        }

                        var color = data.getItemVisual(idx, 'color');

                        var legendSymbolType = 'roundRect';

                        var itemGroup = this._createItem(
                            name, legendItemMap[name], legendModel,
                            legendSymbolType, null,
                            itemAlign, color,
                            selectMode
                        );

                        itemGroup.on('click', curry(dispatchSelectAction, name, api))
                            // FIXME Should not specify the series name
                            .on('mouseover', curry(dispatchHighlightAction, seriesModel, name, api))
                            .on('mouseout', curry(dispatchDownplayAction, seriesModel, name, api));

                        legendDrawedMap[name] = true;
                    }, false, this);
                }
            }, this);

            listComponentHelper.layout(group, legendModel, api);
            // Render background after group is layout
            // FIXME
            listComponentHelper.addBackground(group, legendModel);
        },

        _createItem: function (
            name, itemModel, legendModel,
            legendSymbolType, symbolType,
            itemAlign, color, selectMode
        ) {
            var itemWidth = legendModel.get('itemWidth');
            var itemHeight = legendModel.get('itemHeight');

            var isSelected = legendModel.isSelected(name);
            var itemGroup = new graphic.Group();

            var textStyleModel = itemModel.getModel('textStyle');

            var itemIcon = itemModel.get('icon');

            // Use user given icon first
            legendSymbolType = itemIcon || legendSymbolType;
            itemGroup.add(symbolCreator.createSymbol(
                legendSymbolType, 0, 0, itemWidth, itemHeight, isSelected ? color : LEGEND_DISABLE_COLOR
            ));

            // Compose symbols
            // PENDING
            if (!itemIcon && symbolType
                // At least show one symbol, can't be all none
                && ((symbolType !== legendSymbolType) || symbolType == 'none')
            ) {
                var size = itemHeight * 0.8;
                if (symbolType === 'none') {
                    symbolType = 'circle';
                }
                // Put symbol in the center
                itemGroup.add(symbolCreator.createSymbol(
                    symbolType, (itemWidth - size) / 2, (itemHeight - size) / 2, size, size,
                    isSelected ? color : LEGEND_DISABLE_COLOR
                ));
            }

            // Text
            var textX = itemAlign === 'left' ? itemWidth + 5 : -5;
            var textAlign = itemAlign;

            var formatter = legendModel.get('formatter');
            if (typeof formatter === 'string' && formatter) {
                name = formatter.replace('{name}', name);
            }
            else if (typeof formatter === 'function') {
                name = formatter(name);
            }

            var text = new graphic.Text({
                style: {
                    text: name,
                    x: textX,
                    y: itemHeight / 2,
                    fill: isSelected ? textStyleModel.getTextColor() : LEGEND_DISABLE_COLOR,
                    textFont: textStyleModel.getFont(),
                    textAlign: textAlign,
                    textVerticalAlign: 'middle'
                }
            });
            itemGroup.add(text);

            // Add a invisible rect to increase the area of mouse hover
            itemGroup.add(new graphic.Rect({
                shape: itemGroup.getBoundingRect(),
                invisible: true
            }));

            itemGroup.eachChild(function (child) {
                child.silent = !selectMode;
            });

            this.group.add(itemGroup);

            graphic.setHoverStyle(itemGroup);

            return itemGroup;
        }
    });
});<commit_msg>Improve legend with series has legendDataProvider, like pie, funnel etc. Fix #3039<commit_after>define(function (require) {

    var zrUtil = require('zrender/core/util');
    var symbolCreator = require('../../util/symbol');
    var graphic = require('../../util/graphic');
    var listComponentHelper = require('../helper/listComponent');

    var curry = zrUtil.curry;

    var LEGEND_DISABLE_COLOR = '#ccc';

    function dispatchSelectAction(name, api) {
        api.dispatchAction({
            type: 'legendToggleSelect',
            name: name
        });
    }

    function dispatchHighlightAction(seriesModel, dataName, api) {
        seriesModel.get('legendHoverLink') && api.dispatchAction({
            type: 'highlight',
            seriesName: seriesModel.name,
            name: dataName
        });
    }

    function dispatchDownplayAction(seriesModel, dataName, api) {
        seriesModel.get('legendHoverLink') && api.dispatchAction({
            type: 'downplay',
            seriesName: seriesModel.name,
            name: dataName
        });
    }

    return require('../../echarts').extendComponentView({

        type: 'legend',

        init: function () {
            this._symbolTypeStore = {};
        },

        render: function (legendModel, ecModel, api) {
            var group = this.group;
            group.removeAll();

            if (!legendModel.get('show')) {
                return;
            }

            var selectMode = legendModel.get('selectedMode');
            var itemAlign = legendModel.get('align');

            if (itemAlign === 'auto') {
                itemAlign = (legendModel.get('left') === 'right'
                    && legendModel.get('orient') === 'vertical')
                    ? 'right' : 'left';
            }

            var legendDrawedMap = {};

            zrUtil.each(legendModel.getData(), function (itemModel) {
                var name = itemModel.get('name');

                // Use empty string or \n as a newline string
                if (name === '' || name === '\n') {
                    group.add(new graphic.Group({
                        newline: true
                    }));
                    return;
                }

                var seriesModel = ecModel.getSeriesByName(name)[0];

                if (legendDrawedMap[name]) {
                    // Series not exists
                    return;
                }

                // Series legend
                if (seriesModel) {
                    var data = seriesModel.getData();
                    var color = data.getVisual('color');

                    // If color is a callback function
                    if (typeof color === 'function') {
                        // Use the first data
                        color = color(seriesModel.getDataParams(0));
                    }

                    // Using rect symbol defaultly
                    var legendSymbolType = data.getVisual('legendSymbol') || 'roundRect';
                    var symbolType = data.getVisual('symbol');

                    var itemGroup = this._createItem(
                        name, itemModel, legendModel,
                        legendSymbolType, symbolType,
                        itemAlign, color,
                        selectMode
                    );

                    itemGroup.on('click', curry(dispatchSelectAction, name, api))
                        .on('mouseover', curry(dispatchHighlightAction, seriesModel, '', api))
                        .on('mouseout', curry(dispatchDownplayAction, seriesModel, '', api));

                    legendDrawedMap[name] = true;
                }
                else {
                    // Data legend of pie, funnel
                    ecModel.eachRawSeries(function (seriesModel) {
                        // In case multiple series has same data name
                        if (legendDrawedMap[name]) {
                            return;
                        }
                        if (seriesModel.legendDataProvider) {
                            var data = seriesModel.legendDataProvider();
                            var idx = data.indexOfName(name);

                            var color = data.getItemVisual(idx, 'color');

                            var legendSymbolType = 'roundRect';

                            var itemGroup = this._createItem(
                                name, itemModel, legendModel,
                                legendSymbolType, null,
                                itemAlign, color,
                                selectMode
                            );

                            itemGroup.on('click', curry(dispatchSelectAction, name, api))
                                // FIXME Should not specify the series name
                                .on('mouseover', curry(dispatchHighlightAction, seriesModel, name, api))
                                .on('mouseout', curry(dispatchDownplayAction, seriesModel, name, api));

                            legendDrawedMap[name] = true;
                        }
                    }, this);
                }
            }, this);

            listComponentHelper.layout(group, legendModel, api);
            // Render background after group is layout
            // FIXME
            listComponentHelper.addBackground(group, legendModel);
        },

        _createItem: function (
            name, itemModel, legendModel,
            legendSymbolType, symbolType,
            itemAlign, color, selectMode
        ) {
            var itemWidth = legendModel.get('itemWidth');
            var itemHeight = legendModel.get('itemHeight');

            var isSelected = legendModel.isSelected(name);
            var itemGroup = new graphic.Group();

            var textStyleModel = itemModel.getModel('textStyle');

            var itemIcon = itemModel.get('icon');

            // Use user given icon first
            legendSymbolType = itemIcon || legendSymbolType;
            itemGroup.add(symbolCreator.createSymbol(
                legendSymbolType, 0, 0, itemWidth, itemHeight, isSelected ? color : LEGEND_DISABLE_COLOR
            ));

            // Compose symbols
            // PENDING
            if (!itemIcon && symbolType
                // At least show one symbol, can't be all none
                && ((symbolType !== legendSymbolType) || symbolType == 'none')
            ) {
                var size = itemHeight * 0.8;
                if (symbolType === 'none') {
                    symbolType = 'circle';
                }
                // Put symbol in the center
                itemGroup.add(symbolCreator.createSymbol(
                    symbolType, (itemWidth - size) / 2, (itemHeight - size) / 2, size, size,
                    isSelected ? color : LEGEND_DISABLE_COLOR
                ));
            }

            // Text
            var textX = itemAlign === 'left' ? itemWidth + 5 : -5;
            var textAlign = itemAlign;

            var formatter = legendModel.get('formatter');
            if (typeof formatter === 'string' && formatter) {
                name = formatter.replace('{name}', name);
            }
            else if (typeof formatter === 'function') {
                name = formatter(name);
            }

            var text = new graphic.Text({
                style: {
                    text: name,
                    x: textX,
                    y: itemHeight / 2,
                    fill: isSelected ? textStyleModel.getTextColor() : LEGEND_DISABLE_COLOR,
                    textFont: textStyleModel.getFont(),
                    textAlign: textAlign,
                    textVerticalAlign: 'middle'
                }
            });
            itemGroup.add(text);

            // Add a invisible rect to increase the area of mouse hover
            itemGroup.add(new graphic.Rect({
                shape: itemGroup.getBoundingRect(),
                invisible: true
            }));

            itemGroup.eachChild(function (child) {
                child.silent = !selectMode;
            });

            this.group.add(itemGroup);

            graphic.setHoverStyle(itemGroup);

            return itemGroup;
        }
    });
});<|endoftext|>