<commit_before>import React, {Component, PropTypes} from 'react'

import _ from 'lodash'
import uuid from 'node-uuid'

import ResizeContainer from 'shared/components/ResizeContainer'
import QueryMaker from 'src/dashboards/components/QueryMaker'
import Visualization from 'src/dashboards/components/Visualization'
import OverlayControls from 'src/dashboards/components/OverlayControls'
import DisplayOptions from 'src/dashboards/components/DisplayOptions'

import * as queryModifiers from 'src/utils/queryTransitions'

import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import {buildQuery} from 'utils/influxql'
import {getQueryConfig} from 'shared/apis'

import {
  removeUnselectedTemplateValues,
  TYPE_QUERY_CONFIG,
} from 'src/dashboards/constants'
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'shared/constants'
import {
  COLOR_TYPE_THRESHOLD,
  MAX_THRESHOLDS,
  DEFAULT_COLORS,
  GAUGE_COLORS,
} from 'src/dashboards/constants/gaugeColors'

class CellEditorOverlay extends Component {
  constructor(props) {
    super(props)

    const {cell: {name, type, queries, axes}, sources} = props

    let source = _.get(queries, ['0', 'source'], null)
    source = sources.find(s => s.links.self === source) || props.source

    const queriesWorkingDraft = _.cloneDeep(
      queries.map(({queryConfig}) => ({
        ...queryConfig,
        id: uuid.v4(),
        source,
      }))
    )

    this.state = {
      cellWorkingName: name,
      cellWorkingType: type,
      queriesWorkingDraft,
      activeQueryIndex: 0,
      isDisplayOptionsTabActive: false,
      axes,
      colors: DEFAULT_COLORS,
    }
  }

  componentWillReceiveProps(nextProps) {
    const {status, queryID} = this.props.queryStatus
    const nextStatus = nextProps.queryStatus
    if (nextStatus.status && nextStatus.queryID) {
      if (nextStatus.queryID !== queryID || nextStatus.status !== status) {
        const nextQueries = this.state.queriesWorkingDraft.map(
          q => (q.id === queryID ? {...q, status: nextStatus.status} : q)
        )
        this.setState({queriesWorkingDraft: nextQueries})
      }
    }
  }

  handleAddThreshold = () => {
    const {colors} = this.state

    if (colors.length < MAX_THRESHOLDS) {
      const randomColor = Math.floor(Math.random() * GAUGE_COLORS.length)

      const newThreshold = {
        type: COLOR_TYPE_THRESHOLD,
        id: uuid.v4(),
        value: '50',
        hex: GAUGE_COLORS[randomColor].hex,
        name: GAUGE_COLORS[randomColor].name,
      }

      this.setState({colors: [...colors, newThreshold]})
    }
  }

  handleChooseColor = threshold => chosenColor => {
    const {colors} = this.state

    const newColors = colors.map(
      color =>
        color.id === threshold.id
          ? {...color, hex: chosenColor.hex, name: chosenColor.name}
          : color
    )

    this.setState({colors: newColors})
  }

  handleChangeColorValue = threshold => e => {
    const {colors} = this.state

    const newColors = colors.map(
      color =>
        color.id === threshold.id ? {...color, value: e.target.value} : color
    )

    this.setState({colors: newColors})
  }

  queryStateReducer = queryModifier => (queryID, ...payload) => {
    const {queriesWorkingDraft} = this.state
    const query = queriesWorkingDraft.find(q => q.id === queryID)

    const nextQuery = queryModifier(query, ...payload)

    const nextQueries = queriesWorkingDraft.map(
      q =>
        q.id === query.id
          ? {...nextQuery, source: this.nextSource(q, nextQuery)}
          : q
    )

    this.setState({queriesWorkingDraft: nextQueries})
  }

  handleSetYAxisBoundMin = min => {
    const {axes} = this.state
    const {y: {bounds: [, max]}} = axes

    this.setState({
      axes: {...axes, y: {...axes.y, bounds: [min, max]}},
    })
  }

  handleSetYAxisBoundMax = max => {
    const {axes} = this.state
    const {y: {bounds: [min]}} = axes

    this.setState({
      axes: {...axes, y: {...axes.y, bounds: [min, max]}},
    })
  }

  handleSetLabel = label => {
    const {axes} = this.state

    this.setState({axes: {...axes, y: {...axes.y, label}}})
  }

  handleSetPrefixSuffix = e => {
    const {axes} = this.state
    const {prefix, suffix} = e.target.form

    this.setState({
      axes: {
        ...axes,
        y: {
          ...axes.y,
          prefix: prefix.value,
          suffix: suffix.value,
        },
      },
    })
  }

  handleAddQuery = () => {
    const {queriesWorkingDraft} = this.state
    const newIndex = queriesWorkingDraft.length

    this.setState({
      queriesWorkingDraft: [
        ...queriesWorkingDraft,
        defaultQueryConfig({id: uuid.v4()}),
      ],
    })
    this.handleSetActiveQueryIndex(newIndex)
  }

  handleDeleteQuery = index => {
    const nextQueries = this.state.queriesWorkingDraft.filter(
      (__, i) => i !== index
    )
    this.setState({queriesWorkingDraft: nextQueries})
  }

  handleSaveCell = () => {
    const {
      queriesWorkingDraft,
      cellWorkingType: type,
      cellWorkingName: name,
      axes,
    } = this.state

    const {cell} = this.props

    const queries = queriesWorkingDraft.map(q => {
      const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
      const query = q.rawText || buildQuery(TYPE_QUERY_CONFIG, timeRange, q)

      return {
        queryConfig: q,
        query,
        source: _.get(q, ['source', 'links', 'self'], null),
      }
    })

    this.props.onSave({
      ...cell,
      name,
      type,
      queries,
      axes,
    })
  }

  handleSelectGraphType = graphType => () => {
    this.setState({cellWorkingType: graphType})
  }

  handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => {
    this.setState({isDisplayOptionsTabActive})
  }

  handleSetActiveQueryIndex = activeQueryIndex => {
    this.setState({activeQueryIndex})
  }

  handleSetBase = base => () => {
    const {axes} = this.state

    this.setState({
      axes: {
        ...axes,
        y: {
          ...axes.y,
          base,
        },
      },
    })
  }

  handleCellRename = newName => {
    this.setState({cellWorkingName: newName})
  }

  handleSetScale = scale => () => {
    const {axes} = this.state

    this.setState({
      axes: {
        ...axes,
        y: {
          ...axes.y,
          scale,
        },
      },
    })
  }

  handleSetQuerySource = source => {
    const queriesWorkingDraft = this.state.queriesWorkingDraft.map(q => ({
      ..._.cloneDeep(q),
      source,
    }))

    this.setState({queriesWorkingDraft})
  }

  getActiveQuery = () => {
    const {queriesWorkingDraft, activeQueryIndex} = this.state
    const activeQuery = queriesWorkingDraft[activeQueryIndex]
    const defaultQuery = queriesWorkingDraft[0]

    return activeQuery || defaultQuery
  }

  handleEditRawText = async (url, id, text) => {
    const templates = removeUnselectedTemplateValues(this.props.templates)

    // use this as the handler passed into fetchTimeSeries to update a query status
    try {
      const {data} = await getQueryConfig(url, [{query: text, id}], templates)
      const config = data.queries.find(q => q.id === id)
      const nextQueries = this.state.queriesWorkingDraft.map(
        q => (q.id === id ? {...config.queryConfig, source: q.source} : q)
      )
      this.setState({queriesWorkingDraft: nextQueries})
    } catch (error) {
      console.error(error)
    }
  }

  formatSources = this.props.sources.map(s => ({
    ...s,
    text: `${s.name} @ ${s.url}`,
  }))

  findSelectedSource = () => {
    const {source} = this.props
    const sources = this.formatSources
    const query = _.get(this.state.queriesWorkingDraft, 0, false)

    if (!query || !query.source) {
      const defaultSource = sources.find(s => s.id === source.id)
      return (defaultSource && defaultSource.text) || 'No sources'
    }

    const selected = sources.find(s => s.id === query.source.id)
    return (selected && selected.text) || 'No sources'
  }

  getSource = () => {
    const {source, sources} = this.props
    const query = _.get(this.state.queriesWorkingDraft, 0, false)

    if (!query || !query.source) {
      return source
    }

    const querySource = sources.find(s => s.id === query.source.id)
    return querySource || source
  }

  nextSource = (prevQuery, nextQuery) => {
    if (nextQuery.source) {
      return nextQuery.source
    }

    return prevQuery.source
  }

  render() {
    const {
      onCancel,
      templates,
      timeRange,
      autoRefresh,
      editQueryStatus,
    } = this.props

    const {
      axes,
      colors,
      activeQueryIndex,
      cellWorkingName,
      cellWorkingType,
      isDisplayOptionsTabActive,
      queriesWorkingDraft,
    } = this.state

    const queryActions = {
      editRawTextAsync: this.handleEditRawText,
      ..._.mapValues(queryModifiers, qm => this.queryStateReducer(qm)),
    }

    const isQuerySavable = query =>
      (!!query.measurement && !!query.database && !!query.fields.length) ||
      !!query.rawText

    return (

        <ResizeContainer
          containerClass="resizer--full-size"
          minTopHeight={MINIMUM_HEIGHTS.visualization}
          minBottomHeight={MINIMUM_HEIGHTS.queryMaker}
          initialTopHeight={INITIAL_HEIGHTS.visualization}
          initialBottomHeight={INITIAL_HEIGHTS.queryMaker}
        >
          <Visualization
            axes={axes}
            colors={colors}
            type={cellWorkingType}
            name={cellWorkingName}
            timeRange={timeRange}
            templates={templates}
            autoRefresh={autoRefresh}
            queryConfigs={queriesWorkingDraft}
            editQueryStatus={editQueryStatus}
            onCellRename={this.handleCellRename}
          />
          <CEOBottom>
            <OverlayControls
              onCancel={onCancel}
              queries={queriesWorkingDraft}
              sources={this.formatSources}
              onSave={this.handleSaveCell}
              selected={this.findSelectedSource()}
              onSetQuerySource={this.handleSetQuerySource}
              isSavable={queriesWorkingDraft.every(isQuerySavable)}
              isDisplayOptionsTabActive={isDisplayOptionsTabActive}
              onClickDisplayOptions={this.handleClickDisplayOptionsTab}
            />
            {isDisplayOptionsTabActive
              ? <DisplayOptions
                  axes={axes}
                  colors={colors}
                  onChooseColor={this.handleChooseColor}
                  onChangeColorValue={this.handleChangeColorValue}
                  onAddThreshold={this.handleAddThreshold}
                  onDeleteThreshold={this.handleDeleteThreshold}
                  onSetBase={this.handleSetBase}
                  onSetLabel={this.handleSetLabel}
                  onSetScale={this.handleSetScale}
                  queryConfigs={queriesWorkingDraft}
                  selectedGraphType={cellWorkingType}
                  onSetPrefixSuffix={this.handleSetPrefixSuffix}
                  onSelectGraphType={this.handleSelectGraphType}
                  onSetYAxisBoundMin={this.handleSetYAxisBoundMin}
                  onSetYAxisBoundMax={this.handleSetYAxisBoundMax}
                />
              : <QueryMaker
                  source={this.getSource()}
                  templates={templates}
                  queries={queriesWorkingDraft}
                  actions={queryActions}
                  autoRefresh={autoRefresh}
                  timeRange={timeRange}
                  onDeleteQuery={this.handleDeleteQuery}
                  onAddQuery={this.handleAddQuery}
                  activeQueryIndex={activeQueryIndex}
                  activeQuery={this.getActiveQuery()}
                  setActiveQueryIndex={this.handleSetActiveQueryIndex}
                  initialGroupByTime={AUTO_GROUP_BY}
                />}
          </CEOBottom>
        </ResizeContainer>

    )
  }
}

const CEOBottom = ({children}) =>

    {children}

const {arrayOf, func, node, number, shape, string} = PropTypes

CellEditorOverlay.propTypes = {
  onCancel: func.isRequired,
  onSave: func.isRequired,
  cell: shape({}).isRequired,
  templates: arrayOf(
    shape({
      tempVar: string.isRequired,
    })
  ).isRequired,
  timeRange: shape({
    upper: string,
    lower: string,
  }).isRequired,
  autoRefresh: number.isRequired,
  source: shape({
    links: shape({
      proxy: string.isRequired,
      queries: string.isRequired,
    }).isRequired,
  }).isRequired,
  editQueryStatus: func.isRequired,
  queryStatus: shape({
    queryID: string,
    status: shape({}),
  }).isRequired,
  dashboardID: string.isRequired,
  sources: arrayOf(shape()),
}

CEOBottom.propTypes = {
  children: node,
}

export default CellEditorOverlay
<commit_msg>Add ability to delete a gauge threshold<commit_after>import React, {Component, PropTypes} from 'react'

import _ from 'lodash'
import uuid from 'node-uuid'

import ResizeContainer from 'shared/components/ResizeContainer'
import QueryMaker from 'src/dashboards/components/QueryMaker'
import Visualization from 'src/dashboards/components/Visualization'
import OverlayControls from 'src/dashboards/components/OverlayControls'
import DisplayOptions from 'src/dashboards/components/DisplayOptions'

import * as queryModifiers from 'src/utils/queryTransitions'

import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import {buildQuery} from 'utils/influxql'
import {getQueryConfig} from 'shared/apis'

import {
  removeUnselectedTemplateValues,
  TYPE_QUERY_CONFIG,
} from 'src/dashboards/constants'
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {AUTO_GROUP_BY} from 'shared/constants'
import {
  COLOR_TYPE_THRESHOLD,
  MAX_THRESHOLDS,
  DEFAULT_COLORS,
  GAUGE_COLORS,
} from 'src/dashboards/constants/gaugeColors'

class CellEditorOverlay extends Component {
  constructor(props) {
    super(props)

    const {cell: {name, type, queries, axes}, sources} = props

    let source = _.get(queries, ['0', 'source'], null)
    source = sources.find(s => s.links.self === source) || props.source

    const queriesWorkingDraft = _.cloneDeep(
      queries.map(({queryConfig}) => ({
        ...queryConfig,
        id: uuid.v4(),
        source,
      }))
    )

    this.state = {
      cellWorkingName: name,
      cellWorkingType: type,
      queriesWorkingDraft,
      activeQueryIndex: 0,
      isDisplayOptionsTabActive: false,
      axes,
      colors: DEFAULT_COLORS,
    }
  }

  componentWillReceiveProps(nextProps) {
    const {status, queryID} = this.props.queryStatus
    const nextStatus = nextProps.queryStatus
    if (nextStatus.status && nextStatus.queryID) {
      if (nextStatus.queryID !== queryID || nextStatus.status !== status) {
        const nextQueries = this.state.queriesWorkingDraft.map(
          q => (q.id === queryID ? {...q, status: nextStatus.status} : q)
        )
        this.setState({queriesWorkingDraft: nextQueries})
      }
    }
  }

  handleAddThreshold = () => {
    const {colors} = this.state

    if (colors.length < MAX_THRESHOLDS) {
      const randomColor = Math.floor(Math.random() * GAUGE_COLORS.length)

      const newThreshold = {
        type: COLOR_TYPE_THRESHOLD,
        id: uuid.v4(),
        value: '50',
        hex: GAUGE_COLORS[randomColor].hex,
        name: GAUGE_COLORS[randomColor].name,
      }

      this.setState({colors: [...colors, newThreshold]})
    }
  }

  handleDeleteThreshold = threshold => () => {
    const {colors} = this.state

    const newColors = colors.filter(color => color.id !== threshold.id)

    this.setState({colors: newColors})
  }

  handleChooseColor = threshold => chosenColor => {
    const {colors} = this.state

    const newColors = colors.map(
      color =>
        color.id === threshold.id
          ? {...color, hex: chosenColor.hex, name: chosenColor.name}
          : color
    )

    this.setState({colors: newColors})
  }

  handleChangeColorValue = threshold => e => {
    const {colors} = this.state

    const newColors = colors.map(
      color =>
        color.id === threshold.id ? {...color, value: e.target.value} : color
    )

    this.setState({colors: newColors})
  }

  queryStateReducer = queryModifier => (queryID, ...payload) => {
    const {queriesWorkingDraft} = this.state
    const query = queriesWorkingDraft.find(q => q.id === queryID)

    const nextQuery = queryModifier(query, ...payload)

    const nextQueries = queriesWorkingDraft.map(
      q =>
        q.id === query.id
          ? {...nextQuery, source: this.nextSource(q, nextQuery)}
          : q
    )

    this.setState({queriesWorkingDraft: nextQueries})
  }

  handleSetYAxisBoundMin = min => {
    const {axes} = this.state
    const {y: {bounds: [, max]}} = axes

    this.setState({
      axes: {...axes, y: {...axes.y, bounds: [min, max]}},
    })
  }

  handleSetYAxisBoundMax = max => {
    const {axes} = this.state
    const {y: {bounds: [min]}} = axes

    this.setState({
      axes: {...axes, y: {...axes.y, bounds: [min, max]}},
    })
  }

  handleSetLabel = label => {
    const {axes} = this.state

    this.setState({axes: {...axes, y: {...axes.y, label}}})
  }

  handleSetPrefixSuffix = e => {
    const {axes} = this.state
    const {prefix, suffix} = e.target.form

    this.setState({
      axes: {
        ...axes,
        y: {
          ...axes.y,
          prefix: prefix.value,
          suffix: suffix.value,
        },
      },
    })
  }

  handleAddQuery = () => {
    const {queriesWorkingDraft} = this.state
    const newIndex = queriesWorkingDraft.length

    this.setState({
      queriesWorkingDraft: [
        ...queriesWorkingDraft,
        defaultQueryConfig({id: uuid.v4()}),
      ],
    })
    this.handleSetActiveQueryIndex(newIndex)
  }

  handleDeleteQuery = index => {
    const nextQueries = this.state.queriesWorkingDraft.filter(
      (__, i) => i !== index
    )
    this.setState({queriesWorkingDraft: nextQueries})
  }

  handleSaveCell = () => {
    const {
      queriesWorkingDraft,
      cellWorkingType: type,
      cellWorkingName: name,
      axes,
    } = this.state

    const {cell} = this.props

    const queries = queriesWorkingDraft.map(q => {
      const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
      const query = q.rawText || buildQuery(TYPE_QUERY_CONFIG, timeRange, q)

      return {
        queryConfig: q,
        query,
        source: _.get(q, ['source', 'links', 'self'], null),
      }
    })

    this.props.onSave({
      ...cell,
      name,
      type,
      queries,
      axes,
    })
  }

  handleSelectGraphType = graphType => () => {
    this.setState({cellWorkingType: graphType})
  }

  handleClickDisplayOptionsTab = isDisplayOptionsTabActive => () => {
    this.setState({isDisplayOptionsTabActive})
  }

  handleSetActiveQueryIndex = activeQueryIndex => {
    this.setState({activeQueryIndex})
  }

  handleSetBase = base => () => {
    const {axes} = this.state

    this.setState({
      axes: {
        ...axes,
        y: {
          ...axes.y,
          base,
        },
      },
    })
  }

  handleCellRename = newName => {
    this.setState({cellWorkingName: newName})
  }

  handleSetScale = scale => () => {
    const {axes} = this.state

    this.setState({
      axes: {
        ...axes,
        y: {
          ...axes.y,
          scale,
        },
      },
    })
  }

  handleSetQuerySource = source => {
    const queriesWorkingDraft = this.state.queriesWorkingDraft.map(q => ({
      ..._.cloneDeep(q),
      source,
    }))

    this.setState({queriesWorkingDraft})
  }

  getActiveQuery = () => {
    const {queriesWorkingDraft, activeQueryIndex} = this.state
    const activeQuery = queriesWorkingDraft[activeQueryIndex]
    const defaultQuery = queriesWorkingDraft[0]

    return activeQuery || defaultQuery
  }

  handleEditRawText = async (url, id, text) => {
    const templates = removeUnselectedTemplateValues(this.props.templates)

    // use this as the handler passed into fetchTimeSeries to update a query status
    try {
      const {data} = await getQueryConfig(url, [{query: text, id}], templates)
      const config = data.queries.find(q => q.id === id)
      const nextQueries = this.state.queriesWorkingDraft.map(
        q => (q.id === id ? {...config.queryConfig, source: q.source} : q)
      )
      this.setState({queriesWorkingDraft: nextQueries})
    } catch (error) {
      console.error(error)
    }
  }

  formatSources = this.props.sources.map(s => ({
    ...s,
    text: `${s.name} @ ${s.url}`,
  }))

  findSelectedSource = () => {
    const {source} = this.props
    const sources = this.formatSources
    const query = _.get(this.state.queriesWorkingDraft, 0, false)

    if (!query || !query.source) {
      const defaultSource = sources.find(s => s.id === source.id)
      return (defaultSource && defaultSource.text) || 'No sources'
    }

    const selected = sources.find(s => s.id === query.source.id)
    return (selected && selected.text) || 'No sources'
  }

  getSource = () => {
    const {source, sources} = this.props
    const query = _.get(this.state.queriesWorkingDraft, 0, false)

    if (!query || !query.source) {
      return source
    }

    const querySource = sources.find(s => s.id === query.source.id)
    return querySource || source
  }

  nextSource = (prevQuery, nextQuery) => {
    if (nextQuery.source) {
      return nextQuery.source
    }

    return prevQuery.source
  }

  render() {
    const {
      onCancel,
      templates,
      timeRange,
      autoRefresh,
      editQueryStatus,
    } = this.props

    const {
      axes,
      colors,
      activeQueryIndex,
      cellWorkingName,
      cellWorkingType,
      isDisplayOptionsTabActive,
      queriesWorkingDraft,
    } = this.state

    const queryActions = {
      editRawTextAsync: this.handleEditRawText,
      ..._.mapValues(queryModifiers, qm => this.queryStateReducer(qm)),
    }

    const isQuerySavable = query =>
      (!!query.measurement && !!query.database && !!query.fields.length) ||
      !!query.rawText

    return (

        <ResizeContainer
          containerClass="resizer--full-size"
          minTopHeight={MINIMUM_HEIGHTS.visualization}
          minBottomHeight={MINIMUM_HEIGHTS.queryMaker}
          initialTopHeight={INITIAL_HEIGHTS.visualization}
          initialBottomHeight={INITIAL_HEIGHTS.queryMaker}
        >
          <Visualization
            axes={axes}
            colors={colors}
            type={cellWorkingType}
            name={cellWorkingName}
            timeRange={timeRange}
            templates={templates}
            autoRefresh={autoRefresh}
            queryConfigs={queriesWorkingDraft}
            editQueryStatus={editQueryStatus}
            onCellRename={this.handleCellRename}
          />
          <CEOBottom>
            <OverlayControls
              onCancel={onCancel}
              queries={queriesWorkingDraft}
              sources={this.formatSources}
              onSave={this.handleSaveCell}
              selected={this.findSelectedSource()}
              onSetQuerySource={this.handleSetQuerySource}
              isSavable={queriesWorkingDraft.every(isQuerySavable)}
              isDisplayOptionsTabActive={isDisplayOptionsTabActive}
              onClickDisplayOptions={this.handleClickDisplayOptionsTab}
            />
            {isDisplayOptionsTabActive
              ? <DisplayOptions
                  axes={axes}
                  colors={colors}
                  onChooseColor={this.handleChooseColor}
                  onChangeColorValue={this.handleChangeColorValue}
                  onAddThreshold={this.handleAddThreshold}
                  onDeleteThreshold={this.handleDeleteThreshold}
                  onSetBase={this.handleSetBase}
                  onSetLabel={this.handleSetLabel}
                  onSetScale={this.handleSetScale}
                  queryConfigs={queriesWorkingDraft}
                  selectedGraphType={cellWorkingType}
                  onSetPrefixSuffix={this.handleSetPrefixSuffix}
                  onSelectGraphType={this.handleSelectGraphType}
                  onSetYAxisBoundMin={this.handleSetYAxisBoundMin}
                  onSetYAxisBoundMax={this.handleSetYAxisBoundMax}
                />
              : <QueryMaker
                  source={this.getSource()}
                  templates={templates}
                  queries={queriesWorkingDraft}
                  actions={queryActions}
                  autoRefresh={autoRefresh}
                  timeRange={timeRange}
                  onDeleteQuery={this.handleDeleteQuery}
                  onAddQuery={this.handleAddQuery}
                  activeQueryIndex={activeQueryIndex}
                  activeQuery={this.getActiveQuery()}
                  setActiveQueryIndex={this.handleSetActiveQueryIndex}
                  initialGroupByTime={AUTO_GROUP_BY}
                />}
          </CEOBottom>
        </ResizeContainer>

    )
  }
}

const CEOBottom = ({children}) =>

    {children}

const {arrayOf, func, node, number, shape, string} = PropTypes

CellEditorOverlay.propTypes = {
  onCancel: func.isRequired,
  onSave: func.isRequired,
  cell: shape({}).isRequired,
  templates: arrayOf(
    shape({
      tempVar: string.isRequired,
    })
  ).isRequired,
  timeRange: shape({
    upper: string,
    lower: string,
  }).isRequired,
  autoRefresh: number.isRequired,
  source: shape({
    links: shape({
      proxy: string.isRequired,
      queries: string.isRequired,
    }).isRequired,
  }).isRequired,
  editQueryStatus: func.isRequired,
  queryStatus: shape({
    queryID: string,
    status: shape({}),
  }).isRequired,
  dashboardID: string.isRequired,
  sources: arrayOf(shape()),
}

CEOBottom.propTypes = {
  children: node,
}

export default CellEditorOverlay
<|endoftext|>


<commit_before>module.exports = function (robot) {

    robot.router.head('/hubot/trello-webhooks', function (req, res) {
        robot.logger.info(`trello-webhook HEAD. Status Code: ${res.statusCode}`);
        res.send(200);
    });

    robot.router.post('/hubot/trello-webhooks', function (req, res) {
        let headers = JSON.stringify(req.headers);
        robot.logger.info(`trello-webhook POST. Status Code: ${res.statusCode}\nHeaders: ${headers}`);
        robot.emit("trello-webhook-event", req, res);
        res.send(200);

        var room = "random";
        let payload = req.body;
        let type = payload.action.type;
        // switch (type)
        robot.messageRoom('random', type);

/*addAdminToBoard
addAdminToOrganization
addedAttachmentToCard
addedMemberToCard
addedToBoard
addedToCard
addedToOrganization
cardDueSoon
changeCard
closeBoard
commentCard
createdCard
declinedInvitationToBoard
declinedInvitationToOrganization
invitedToBoard
invitedToOrganization
makeAdminOfBoard
makeAdminOfOrganization
memberJoinedTrello
mentionedOnCard
removedFromBoard
removedFromCard
removedFromOrganization
removedMemberFromCard
unconfirmedInvitedToBoard
unconfirmedInvitedToOrganization
updateCheckItemStateOnCard*/
    });
}
<commit_msg>trello-webhooks<commit_after>module.exports = function (robot) {

    robot.router.head('/hubot/trello-webhooks', function (req, res) {
        robot.logger.info(`trello-webhook HEAD. Status Code: ${res.statusCode}`);
        res.send(200);
    });

    robot.router.post('/hubot/trello-webhooks', function (req, res) {
        let headers = JSON.stringify(req.headers);
        robot.logger.info(`trello-webhook POST. Status Code: ${res.statusCode}\nHeaders: ${headers}`);
        // robot.emit("trello-webhook-event", req, res);
        res.send(200);

        var room = "random";
        let payload = req.body;
        let type = payload.action.type;
        robot.logger.info(payload); // payload sample here: https://github.com/andreash92/trello-webhooks
        switch (type) {
            case 'addAttachmentToCard':
            case 'addChecklistToCard':
            case 'addLabelToCard':
            case 'addMemberToBoard':
            case 'addMemberToCard':
            case 'commentCard':
            case 'convertToCardFromCheckItem':
            case 'copyCard':
            case 'createCard':
            case 'createLabel':
            case 'createCheckItem':
            case 'createLabel':
            case 'createList':
            case 'deleteAttachmentFromCard':
            case 'deleteCard':
            case 'deleteCheckItem':
            case 'deleteComment':
            case 'deleteLabel':
            case 'emailCard':
            case 'moveCardFromBoard':
            case 'moveCardToBoard':
            case 'moveListFromBoard':
            case 'moveListToBoard':
            case 'removeChecklistFromCard':
            case 'removeLabelFromCard':
            case 'removeMemberFromBoard':
            case 'removeMemberFromCard':
            case 'updateBoard':
            case 'updateCard':
            case 'updateCheckItem':
            case 'updateCheckItemStateOnCard':
            case 'updateChecklist':
            case 'updateComment':
            case 'updateLabel':
            case 'updateList':
            case 'voteOnCard':
                robot.messageRoom(room, type.split(/(?=[A-Z])/).join(" ").toLowerCase());
                break;
            default:
                robot.messageRoom(room, 'webhook not found: ' + type.split(/(?=[A-Z])/));
                break;
        }
        robot.messageRoom('random', type);

    });
}
<|endoftext|>


<commit_before>var words = {}

function addToMap(text, value){
    words[text]=value;
  }
function Engine(){
  //NEEDTODEFINE
  words ={};
  this.allText;

  //Method to add individual elements to hashmap
  //this.

  this.loadMap = function(){
    var file="https://raw.githubusercontent.com/tash-had/YHack_2016/master/SentimentEngine/Corpus.txt";
      var rawFile = new XMLHttpRequest();
      rawFile.open("GET", file, false);
      rawFile.onreadystatechange = function (){
          if(rawFile.readyState === 4){
              if(rawFile.status === 200 || rawFile.status == 0){
                  var lines = rawFile.responseText.split('\n');
                  console.log(lines);
                  var line;
                  for(line = 0; line < lines.length; line++){
                      var tokens = [lines[line].substring(0, lines[line].lastIndexOf(" ")), lines[line].substring(lines[line].lastIndexOf(" "), lines[line].length-1)];
                      addToMap(tokens[0],tokens[1]);
                  }
              }
          }
      }
      rawFile.send(null);
  }

  //feed in twitter as text
  this.analyse=function(text){
    var word = this.result.split(' ');
    var total;
    var amount = word.length;
    for (var i=0;i< word.length;i++){

      //check if contained
      if (this.word[i] in words){
        total+=words[word[i]];
      }
      //otherwise dont add anything and subtract from total wrods;
      else{
        amount-=0;
      }
    }
    total/=amount;
    return total;
  }
}

var engine = new Engine();
engine.loadMap();
console.log(words);<commit_msg>fixed whitespace issues<commit_after>var words = {}

function addToMap(text, value){
    words[text]=value;
  }
function Engine(){
  //NEEDTODEFINE
  words ={};
  this.allText;

  //Method to add individual elements to hashmap
  //this.

  this.loadMap = function(){
    var file="https://raw.githubusercontent.com/tash-had/YHack_2016/master/SentimentEngine/Corpus.txt";
      var rawFile = new XMLHttpRequest();
      rawFile.open("GET", file, false);
      rawFile.onreadystatechange = function (){
          if(rawFile.readyState === 4){
              if(rawFile.status === 200 || rawFile.status == 0){
                  var lines = rawFile.responseText.trim().split('\n');
                  console.log(lines.length);
                  var line;
                  for(line = 0; line < lines.length; line++){
                      var tokens = [lines[line].substring(0, lines[line].lastIndexOf(" ")), lines[line].substring(lines[line].lastIndexOf(" "), lines[line].length-1).trim()];
                      addToMap(tokens[0],tokens[1]);
                  }
              }
          }
      }
      rawFile.send(null);
  }

  //feed in twitter as text
  this.analyse=function(text){
    var word = this.result.split(' ');
    var total;
    var amount = word.length;
    for (var i=0;i< word.length;i++){

      //check if contained
      if (this.word[i] in words){
        total+=words[word[i]];
      }
      //otherwise dont add anything and subtract from total wrods;
      else{
        amount-=0;
      }
    }
    total/=amount;
    return total;
  }
}

var engine = new Engine();
engine.loadMap();
console.log(words);<|endoftext|>


<commit_before>(function () {

    var defaults = {
        type: 'image/png',
        fill: '#fff',
        fileName: 'contour.png'
    };
    var browser = { // browser capabilities
        checked: false
    };
    var shim = {
        encodeBase64: undefined, // base 64 encoder
        serializeXml: undefined // xml serializer
    }; // shims for less capable browsers

    var exportable = function () {
        var ignoreDiff = { // css rules to ignore for diff
            cssText: 1,
            parentRule: 1
        };
        var self; // reference to this contour

        // interface

        return {
            init: function () {
                self = this;

                if (!browser.checked) checkBrowser();

                return this;
            },

            /**
            * Saves a visualization as an image, triggering a download.
            *
            * `type` specfies the mime type of the image. See http://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support for browser support. (Default: 'image/png'.)
            * `fileName` specifies the fileName for the download. (Default: 'contour.png'.)
            *
            * ###Example:
            *
            *     var contour = new Contour(...)
            *         ...
            *         .exportable()
            *         .render();
            *     document.getElementById('save').onclick = function () {
            *         contour.download({
            *             fileName: 'contour.png',
            *             width: 640
            *         });
            *
            * @name download
            * @param {object} options Configuration options specific to saving the image.
            */
            download: function (options) {
                exportImage(options, 'download');

                return this;
            },

            /**
            * Saves a visualization as an image.
            *
            * `type` specfies the mime type of the image. See http://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support for browser support. (Default: 'image/png'.)
            * `target` specifies a selector for the container. (For example: `target: '#vis'` will insert the image in `<div id="vis"></div>`.)
            *
            * ###Example:
            *
            *     var contour = new Contour(...)
            *         ...
            *         .exportable()
            *         .render();
            *     document.getElementById('save').onclick = function () {
            *         contour.place({
            *             target: '#image'
            *         });
            *
            * @name place
            * @param {object} options Configuration options specific to saving the image.
            */
            place: function (options) {
                exportImage(options, 'place');

                return this;
            }
        };

        // svg to canvas export function
        // adapted from https://github.com/sampumon/SVG.toDataURL
        // which based on http://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/#svg_to_canvas

        // exports svg to canvas
        function getSvgDataUrl(svg, options, dataUrlCreated) {
            switch (options.type) {
                case 'image/svg+xml':
                    return exportSvg();

                default: // 'image/png' or 'image/jpeg'
                    return exportImage();
            }

            function encodeBase64DataUrl(svgXml) {
                // https://developer.mozilla.org/en/DOM/window.btoa
                return 'data:image/svg+xml;base64,' + shim.encodeBase64(svgXml);
            }

            function exportSvg() {
                var svgXml = shim.serializeXml(svg);
                var svgDataUrl = encodeBase64DataUrl(svgXml);

                dataUrlCreated(svgDataUrl, function () {});
            }

            function exportImage() {
                var canvas = document.createElement('canvas');
                var context = canvas.getContext('2d');

                var svgXml = shim.serializeXml(svg);

                if (window.canvg) { // use canvg renderer for image export
                    renderImageCanvg();
                } else { // use native renderer for image export (this might fail)
                    renderImageNative();
                }

                function imageRendered() {
                    var imageDataUrl = canvas.toDataURL(options.type);

                    dataUrlCreated(imageDataUrl);
                }

                function renderImageNative() {
                    var svgImg = new Image();
                    svgImg.src = encodeBase64DataUrl(svgXml);

                    svgImg.onload = function () {
                        canvas.width = svgImg.width;
                        canvas.height = svgImg.height;

                        if (options.fill) {
                            context.fillStyle = options.fill;
                            context.fillRect(0, 0, svgImg.width, svgImg.height);
                        }

                        context.drawImage(svgImg, 0, 0);

                        imageRendered();
                    };

                    svgImg.onerror = function () {
                        console.log('Cannot export image');
                    };
                }

                function renderImageCanvg() {
                    // NOTE: canvg gets the svg element dimensions incorrectly if not specified as attributes
                    // NOTE: this canvg call is synchronous and blocks
                    canvg(canvas, svgXml, {
                        ignoreMouse: true,
                        ignoreAnimation: true,
                        offsetX: undefined,
                        offsetY: undefined,
                        scaleWidth: undefined,
                        scaleHeight: undefined,
                        renderdataUrlCreated: imageRendered
                    });
                }
            }
        }

        // clone svg in isolation with styles directly applied
        function createSvgClone(svgNode, svgCloned) {
            createIsolatedNode(function (nodeClone, destroyIsolatedNode) {
                cloneNodes(svgNode, nodeClone); // clone nodes and apply styles directly to each node

                svgCloned(d3.select(nodeClone).select('svg').node(), destroyIsolatedNode);
            });

            // compare computed styles at this node and apply the differences directly
            function applyStyles(sourceNode, targetNode) {
                var sourceStyle = window.getComputedStyle(sourceNode);
                var targetStyle = window.getComputedStyle(targetNode);

                for (var prop in sourceStyle) {
                    if (!ignoreDiff[prop] && !isFinite(prop)) { // note that checking for sourceStyle.hasOwnProperty(prop) eliminates all valid style properties in firefox
                        if (targetStyle[prop] !== sourceStyle[prop]) {
                            targetNode.style[prop] = sourceStyle[prop];
                        }
                    }
                }
            }

            // clone nodes and apply styles directly to each node
            function cloneNodes(sourceNode, targetNode) {
                var newNode = sourceNode.cloneNode(false);
                targetNode.appendChild(newNode);

                if (!sourceNode.tagName) return; // skip inner text

                applyStyles(sourceNode, newNode); // compare computed styles at this node and apply the differences directly

                _.each(sourceNode.childNodes, function (childNode) {
                    cloneNodes(childNode, newNode); // clone each child node and apply styles
                });
            }

            function createIsolatedNode(nodeLoaded) {
                var iframe = document.body.appendChild(document.createElement('iframe'));
                iframe.style.visibility = 'hidden';
                var iframeWindow = iframe.contentWindow;
                var iframeDocument = iframeWindow.document;

                iframe.onload = function () {
                    var nodeClone = iframeDocument.createElement('div');
                    iframeDocument.body.appendChild(nodeClone);

                    var destroyIframe = function () {
                        iframeDocument.body.removeChild(nodeClone);
                        document.body.removeChild(iframe); // destroy clone
                    };

                    nodeLoaded(nodeClone, destroyIframe);
                };

                iframeDocument.open();
                iframeDocument.write('<!DOCTYPE html>');
                iframeDocument.write('');
                iframeDocument.close();
            }
        }

        function getProportionedBounds(original, specified) {
            if (specified.width && specified.height) {
                return specified;
            } else if (specified.width) {
                return {
                    width: specified.width,
                    height: specified.width * original.height / original.width
                };
            } else if (specified.height) {
                return {
                    width: specified.height * original.width / original.height,
                    height: specified.height
                };
            } else {
                return original;
            }
        }

        function exportImage(options, exporter) {
            options = options || {};
            _.defaults(options, defaults); // merge configuration options with defaults

            var svgNode = self.container.select('svg').node();
            var bounds = svgNode.getBoundingClientRect(); // get bounds from original svg
            var boundsClone = getProportionedBounds(bounds, options);

            createSvgClone(svgNode, performExport); // clone svg in isolation with styles directly applied

            function performExport(svgNodeClone, destroySvgClone) {
                svgNodeClone.setAttribute('width', boundsClone.width);
                svgNodeClone.setAttribute('height', boundsClone.height);

                getSvgDataUrl(svgNodeClone, options, function (url, revokeUrl) {
                    destroySvgClone();

                    // call exporter function
                    var exporters = {
                        'download': function () {
                            if (browser.aDownloads) {
                                // make a link to download and click it
                                var a = document.createElement('a');
                                a.download = options.fileName;
                                a.href = url;
                                document.body.appendChild(a);
                                a.click();
                                document.body.removeChild(a);
                            } else {
                                window.open(url);
                            }
                        },
                        'place': function () {
                            var img = document.createElement('img');
                            img.src = url;
                            d3.select(options.target).node().appendChild(img);
                        }
                    };
                    exporters[exporter](); // call exporter function

                    revokeUrl();
                });
            }
        }
    };

    function checkBrowser() {
        browser.checked = true;

        checkADownloads();
        checkEncodesBase64();
        checkSerializesXml();
        checkExportsSvg();

        function checkADownloads() {
            browser.aDownloads = document.createElement('a').download !== undefined;
        }

        function checkEncodesBase64() {
            browser.encodesBase64 = !!window.btoa;

            if (browser.encodesBase64) {
                shim.encodeBase64 = function (s) { // use window.btoa for base64 encoding
                    return btoa(s); // note that it won't work as just `shim.encodeBase64 = btoa;`, therefore it needs to be wrapped
                };
            } else {
                shim.encodeBase64 = base64Encode; // use custom base64 encoder
            }

            // base64 encode
            // adapted from http://www.webtoolkit.info/
            function base64Encode(input) {
                var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
                var output = '';
                var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
                var i = 0;

                input = utf8_encode(input);

                while (i < input.length) {
                    chr1 = input.charCodeAt(i++);
                    chr2 = input.charCodeAt(i++);
                    chr3 = input.charCodeAt(i++);

                    enc1 = chr1 >> 2;
                    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                    enc4 = chr3 & 63;

                    if (isNaN(chr2)) {
                        enc3 = enc4 = 64;
                    } else if (isNaN(chr3)) {
                        enc4 = 64;
                    }

                    output = output +
                        keyStr.charAt(enc1) +
                        keyStr.charAt(enc2) +
                        keyStr.charAt(enc3) +
                        keyStr.charAt(enc4);
                }

                return output;

            }

            function utf8_encode(string) {
                string = string.replace(/\r\n/g, '\n');
                var utftext = '';

                for (var n = 0; n < string.length; n++) {
                    var c = string.charCodeAt(n);

                    if (c < 128) {
                        utftext += String.fromCharCode(c);
                    }
                    else if((c > 127) && (c < 2048)) {
                        utftext += String.fromCharCode((c >> 6) | 192);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }
                    else {
                        utftext += String.fromCharCode((c >> 12) | 224);
                        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }
                }

                return utftext;
            }
        }

        function checkSerializesXml() {
            browser.serializesXml = !!window.XMLSerializer;

            if (browser.serializesXml) {
                shim.serializeXml = function (xml) { // use standard XMLSerializer.serializeToString
                    return (new XMLSerializer()).serializeToString(xml);
                };
            } else {
                shim.serializeXml = serializeXmlToString; // use custom serializeXmlToString for IE9
            }

            // quick-n-serialize an SVG dom, needed for IE9 where there's no XMLSerializer nor SVG.xml
            // s: SVG dom, which is the <svg> elemennt
            function serializeXmlToString(s) {
                var out = '<' + s.nodeName;
                for (var i = 0; i < s.attributes.length; i++) {
                    out += ' ' + s.attributes[i].name + '=' + '"' + s.attributes[i].value + '"';
                }

                if (s.hasChildNodes()) {
                    out += '>\n';
                    for (var j = 0; j < s.childNodes.length; j++) {
                        out += serializeXmlToString(s.childNodes[j]);
                    }
                    out += '</' + s.nodeName + '>' + '\n';
                } else out += ' />\n';

                return out;
            }
        }

        function checkExportsSvg() {
            browser.exportsSvg = false;

            var iframe = document.body.appendChild(document.createElement('iframe'));
            iframe.style.visibility = 'hidden';
            var doc = iframe.contentWindow.document;

            iframe.onload = function () {
                try {
                    var svg = doc.querySelector('svg');
                    var img = doc.querySelector('img');
                    var canvas = doc.querySelector('canvas');
                    var context = canvas.getContext('2d');
                    canvas.width = img.getAttribute('width') * 1;
                    canvas.height = img.getAttribute('height') * 1;
                    var sourceImg = new Image();
                    sourceImg.width = canvas.width;
                    sourceImg.height = canvas.height;
                    sourceImg.onload = function () {
                        try {
                            context.drawImage(sourceImg, 0, 0, img.width, img.height);
                            img.src = canvas.toDataURL();

                            browser.exportsSvg = true; // yay
                        } catch (e) {}

                        svgExportChecked();
                    };
                    var xml = shim.serializeXml(svg);
                    sourceImg.src = 'data:image/svg+xml,' + encodeURIComponent(xml);
                } catch (e) {
                    svgExportChecked();
                }
            };

            doc.open();
            doc.write('<!DOCTYPE html>');
            doc.write('');
            doc.write('<svg xmlns="http://www.w3.org/2000/svg" width="2" height="2" viewBox="0 0 1 1"><circle r="1" fill="red"/></svg>');
            doc.write('');
            doc.write('<canvas></canvas>');
            doc.write('');
            doc.close();

            function svgExportChecked() {
                document.body.removeChild(iframe);

                if (!browser.exportsSvg) { // load canvg svg renderer for browsers that can't safely export svg
                    _.each([
                        'rgbcolor.js',
                        'StackBlur.js',
                        'canvg.js'
                    ], function (src) {
                        var script = document.createElement('script');
                        script.type = 'text/javascript';
                        script.src = 'http://canvg.googlecode.com/svn/trunk/' + src;
                        document.head.appendChild(script);
                    });
                }
            }
        }
    }

    /**
    * Saves a visualization as an image.
    *
    * ###Example:
    *
    *     var contour = new Contour(...)
    *         ...
    *         .exportable()
    *         .render();
    *     document.getElementById('save').onclick = function () {
    *         contour.download({
    *             fileName: 'contour.png'
    *         });
    *
    * @name exportable
    */
    Contour.expose('exportable', exportable);

})();
<commit_msg>change comment on shims<commit_after>(function () {

    var defaults = {
        type: 'image/png',
        fill: '#fff',
        fileName: 'contour.png'
    };

    var browser = { // browser capabilities
        checked: false
    };
    var shim = { // shims depending on current browser
        encodeBase64: undefined, // base 64 encoder
        serializeXml: undefined // xml serializer
    };

    var exportable = function () {
        var ignoreDiff = { // css rules to ignore for diff
            cssText: 1,
            parentRule: 1
        };
        var self; // reference to this contour

        // interface

        return {
            init: function () {
                self = this;

                if (!browser.checked) checkBrowser();

                return this;
            },

            /**
            * Saves a visualization as an image, triggering a download.
            *
            * `type` specfies the mime type of the image. See http://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support for browser support. (Default: 'image/png'.)
            * `fileName` specifies the fileName for the download. (Default: 'contour.png'.)
            *
            * ###Example:
            *
            *     var contour = new Contour(...)
            *         ...
            *         .exportable()
            *         .render();
            *     document.getElementById('save').onclick = function () {
            *         contour.download({
            *             fileName: 'contour.png',
            *             width: 640
            *         });
            *
            * @name download
            * @param {object} options Configuration options specific to saving the image.
            */
            download: function (options) {
                exportImage(options, 'download');

                return this;
            },

            /**
            * Saves a visualization as an image.
            *
            * `type` specfies the mime type of the image. See http://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support for browser support. (Default: 'image/png'.)
            * `target` specifies a selector for the container. (For example: `target: '#vis'` will insert the image in `<div id="vis"></div>`.)
            *
            * ###Example:
            *
            *     var contour = new Contour(...)
            *         ...
            *         .exportable()
            *         .render();
            *     document.getElementById('save').onclick = function () {
            *         contour.place({
            *             target: '#image'
            *         });
            *
            * @name place
            * @param {object} options Configuration options specific to saving the image.
            */
            place: function (options) {
                exportImage(options, 'place');

                return this;
            }
        };

        // svg to canvas export function
        // adapted from https://github.com/sampumon/SVG.toDataURL
        // which based on http://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/#svg_to_canvas

        // exports svg to canvas
        function getSvgDataUrl(svg, options, dataUrlCreated) {
            switch (options.type) {
                case 'image/svg+xml':
                    return exportSvg();

                default: // 'image/png' or 'image/jpeg'
                    return exportImage();
            }

            function encodeBase64DataUrl(svgXml) {
                // https://developer.mozilla.org/en/DOM/window.btoa
                return 'data:image/svg+xml;base64,' + shim.encodeBase64(svgXml);
            }

            function exportSvg() {
                var svgXml = shim.serializeXml(svg);
                var svgDataUrl = encodeBase64DataUrl(svgXml);

                dataUrlCreated(svgDataUrl, function () {});
            }

            function exportImage() {
                var canvas = document.createElement('canvas');
                var context = canvas.getContext('2d');

                var svgXml = shim.serializeXml(svg);

                if (window.canvg) { // use canvg renderer for image export
                    renderImageCanvg();
                } else { // use native renderer for image export (this might fail)
                    renderImageNative();
                }

                function imageRendered() {
                    var imageDataUrl = canvas.toDataURL(options.type);

                    dataUrlCreated(imageDataUrl);
                }

                function renderImageNative() {
                    var svgImg = new Image();
                    svgImg.src = encodeBase64DataUrl(svgXml);

                    svgImg.onload = function () {
                        canvas.width = svgImg.width;
                        canvas.height = svgImg.height;

                        if (options.fill) {
                            context.fillStyle = options.fill;
                            context.fillRect(0, 0, svgImg.width, svgImg.height);
                        }

                        context.drawImage(svgImg, 0, 0);

                        imageRendered();
                    };

                    svgImg.onerror = function () {
                        console.log('Cannot export image');
                    };
                }

                function renderImageCanvg() {
                    // NOTE: canvg gets the svg element dimensions incorrectly if not specified as attributes
                    // NOTE: this canvg call is synchronous and blocks
                    canvg(canvas, svgXml, {
                        ignoreMouse: true,
                        ignoreAnimation: true,
                        offsetX: undefined,
                        offsetY: undefined,
                        scaleWidth: undefined,
                        scaleHeight: undefined,
                        renderdataUrlCreated: imageRendered
                    });
                }
            }
        }

        // clone svg in isolation with styles directly applied
        function createSvgClone(svgNode, svgCloned) {
            createIsolatedNode(function (nodeClone, destroyIsolatedNode) {
                cloneNodes(svgNode, nodeClone); // clone nodes and apply styles directly to each node

                svgCloned(d3.select(nodeClone).select('svg').node(), destroyIsolatedNode);
            });

            // compare computed styles at this node and apply the differences directly
            function applyStyles(sourceNode, targetNode) {
                var sourceStyle = window.getComputedStyle(sourceNode);
                var targetStyle = window.getComputedStyle(targetNode);

                for (var prop in sourceStyle) {
                    if (!ignoreDiff[prop] && !isFinite(prop)) { // note that checking for sourceStyle.hasOwnProperty(prop) eliminates all valid style properties in firefox
                        if (targetStyle[prop] !== sourceStyle[prop]) {
                            targetNode.style[prop] = sourceStyle[prop];
                        }
                    }
                }
            }

            // clone nodes and apply styles directly to each node
            function cloneNodes(sourceNode, targetNode) {
                var newNode = sourceNode.cloneNode(false);
                targetNode.appendChild(newNode);

                if (!sourceNode.tagName) return; // skip inner text

                applyStyles(sourceNode, newNode); // compare computed styles at this node and apply the differences directly

                _.each(sourceNode.childNodes, function (childNode) {
                    cloneNodes(childNode, newNode); // clone each child node and apply styles
                });
            }

            function createIsolatedNode(nodeLoaded) {
                var iframe = document.body.appendChild(document.createElement('iframe'));
                iframe.style.visibility = 'hidden';
                var iframeWindow = iframe.contentWindow;
                var iframeDocument = iframeWindow.document;

                iframe.onload = function () {
                    var nodeClone = iframeDocument.createElement('div');
                    iframeDocument.body.appendChild(nodeClone);

                    var destroyIframe = function () {
                        iframeDocument.body.removeChild(nodeClone);
                        document.body.removeChild(iframe); // destroy clone
                    };

                    nodeLoaded(nodeClone, destroyIframe);
                };

                iframeDocument.open();
                iframeDocument.write('<!DOCTYPE html>');
                iframeDocument.write('');
                iframeDocument.close();
            }
        }

        function getProportionedBounds(original, specified) {
            if (specified.width && specified.height) {
                return specified;
            } else if (specified.width) {
                return {
                    width: specified.width,
                    height: specified.width * original.height / original.width
                };
            } else if (specified.height) {
                return {
                    width: specified.height * original.width / original.height,
                    height: specified.height
                };
            } else {
                return original;
            }
        }

        function exportImage(options, exporter) {
            options = options || {};
            _.defaults(options, defaults); // merge configuration options with defaults

            var svgNode = self.container.select('svg').node();
            var bounds = svgNode.getBoundingClientRect(); // get bounds from original svg
            var boundsClone = getProportionedBounds(bounds, options);

            createSvgClone(svgNode, performExport); // clone svg in isolation with styles directly applied

            function performExport(svgNodeClone, destroySvgClone) {
                svgNodeClone.setAttribute('width', boundsClone.width);
                svgNodeClone.setAttribute('height', boundsClone.height);

                getSvgDataUrl(svgNodeClone, options, function (url, revokeUrl) {
                    destroySvgClone();

                    // call exporter function
                    var exporters = {
                        'download': function () {
                            if (browser.aDownloads) {
                                // make a link to download and click it
                                var a = document.createElement('a');
                                a.download = options.fileName;
                                a.href = url;
                                document.body.appendChild(a);
                                a.click();
                                document.body.removeChild(a);
                            } else {
                                window.open(url);
                            }
                        },
                        'place': function () {
                            var img = document.createElement('img');
                            img.src = url;
                            d3.select(options.target).node().appendChild(img);
                        }
                    };
                    exporters[exporter](); // call exporter function

                    revokeUrl();
                });
            }
        }
    };

    function checkBrowser() {
        browser.checked = true;

        checkADownloads();
        checkEncodesBase64();
        checkSerializesXml();
        checkExportsSvg();

        function checkADownloads() {
            browser.aDownloads = document.createElement('a').download !== undefined;
        }

        function checkEncodesBase64() {
            browser.encodesBase64 = !!window.btoa;

            if (browser.encodesBase64) {
                shim.encodeBase64 = function (s) { // use window.btoa for base64 encoding
                    return btoa(s); // note that it won't work as just `shim.encodeBase64 = btoa;`, therefore it needs to be wrapped
                };
            } else {
                shim.encodeBase64 = base64Encode; // use custom base64 encoder
            }

            // base64 encode
            // adapted from http://www.webtoolkit.info/
            function base64Encode(input) {
                var keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
                var output = '';
                var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
                var i = 0;

                input = utf8_encode(input);

                while (i < input.length) {
                    chr1 = input.charCodeAt(i++);
                    chr2 = input.charCodeAt(i++);
                    chr3 = input.charCodeAt(i++);

                    enc1 = chr1 >> 2;
                    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                    enc4 = chr3 & 63;

                    if (isNaN(chr2)) {
                        enc3 = enc4 = 64;
                    } else if (isNaN(chr3)) {
                        enc4 = 64;
                    }

                    output = output +
                        keyStr.charAt(enc1) +
                        keyStr.charAt(enc2) +
                        keyStr.charAt(enc3) +
                        keyStr.charAt(enc4);
                }

                return output;

            }

            function utf8_encode(string) {
                string = string.replace(/\r\n/g, '\n');
                var utftext = '';

                for (var n = 0; n < string.length; n++) {
                    var c = string.charCodeAt(n);

                    if (c < 128) {
                        utftext += String.fromCharCode(c);
                    }
                    else if((c > 127) && (c < 2048)) {
                        utftext += String.fromCharCode((c >> 6) | 192);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }
                    else {
                        utftext += String.fromCharCode((c >> 12) | 224);
                        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }
                }

                return utftext;
            }
        }

        function checkSerializesXml() {
            browser.serializesXml = !!window.XMLSerializer;

            if (browser.serializesXml) {
                shim.serializeXml = function (xml) { // use standard XMLSerializer.serializeToString
                    return (new XMLSerializer()).serializeToString(xml);
                };
            } else {
                shim.serializeXml = serializeXmlToString; // use custom serializeXmlToString for IE9
            }

            // quick-n-serialize an SVG dom, needed for IE9 where there's no XMLSerializer nor SVG.xml
            // s: SVG dom, which is the <svg> elemennt
            function serializeXmlToString(s) {
                var out = '<' + s.nodeName;
                for (var i = 0; i < s.attributes.length; i++) {
                    out += ' ' + s.attributes[i].name + '=' + '"' + s.attributes[i].value + '"';
                }

                if (s.hasChildNodes()) {
                    out += '>\n';
                    for (var j = 0; j < s.childNodes.length; j++) {
                        out += serializeXmlToString(s.childNodes[j]);
                    }
                    out += '</' + s.nodeName + '>' + '\n';
                } else out += ' />\n';

                return out;
            }
        }

        function checkExportsSvg() {
            browser.exportsSvg = false;

            var iframe = document.body.appendChild(document.createElement('iframe'));
            iframe.style.visibility = 'hidden';
            var doc = iframe.contentWindow.document;

            iframe.onload = function () {
                try {
                    var svg = doc.querySelector('svg');
                    var img = doc.querySelector('img');
                    var canvas = doc.querySelector('canvas');
                    var context = canvas.getContext('2d');
                    canvas.width = img.getAttribute('width') * 1;
                    canvas.height = img.getAttribute('height') * 1;
                    var sourceImg = new Image();
                    sourceImg.width = canvas.width;
                    sourceImg.height = canvas.height;
                    sourceImg.onload = function () {
                        try {
                            context.drawImage(sourceImg, 0, 0, img.width, img.height);
                            img.src = canvas.toDataURL();

                            browser.exportsSvg = true; // yay
                        } catch (e) {}

                        svgExportChecked();
                    };
                    var xml = shim.serializeXml(svg);
                    sourceImg.src = 'data:image/svg+xml,' + encodeURIComponent(xml);
                } catch (e) {
                    svgExportChecked();
                }
            };

            doc.open();
            doc.write('<!DOCTYPE html>');
            doc.write('');
            doc.write('<svg xmlns="http://www.w3.org/2000/svg" width="2" height="2" viewBox="0 0 1 1"><circle r="1" fill="red"/></svg>');
            doc.write('');
            doc.write('<canvas></canvas>');
            doc.write('');
            doc.close();

            function svgExportChecked() {
                document.body.removeChild(iframe);

                if (!browser.exportsSvg) { // load canvg svg renderer for browsers that can't safely export svg
                    _.each([
                        'rgbcolor.js',
                        'StackBlur.js',
                        'canvg.js'
                    ], function (src) {
                        var script = document.createElement('script');
                        script.type = 'text/javascript';
                        script.src = 'http://canvg.googlecode.com/svn/trunk/' + src;
                        document.head.appendChild(script);
                    });
                }
            }
        }
    }

    /**
    * Saves a visualization as an image.
    *
    * ###Example:
    *
    *     var contour = new Contour(...)
    *         ...
    *         .exportable()
    *         .render();
    *     document.getElementById('save').onclick = function () {
    *         contour.download({
    *             fileName: 'contour.png'
    *         });
    *
    * @name exportable
    */
    Contour.expose('exportable', exportable);

})();
<|endoftext|>