text:
/// <reference path="../_references.ts"/>

module powerbi.visuals {
    import Color = jsCommon.color;

    export interface PlayChartConstructorOptions extends CartesianVisualConstructorOptions {
        isFrozen?: boolean;
    }

    export interface PlayChartDataPoint extends SelectableDataPoint, TooltipEnabledDataPoint, LabelEnabledDataPoint {
        x: any;
        y: any;
        size: any;
        radius: RadiusData;
        fill: string;
        category: string;
    }

    export interface PlayChartData {
        xCol: DataViewMetadataColumn;
        yCol: DataViewMetadataColumn;
        dataPoints: PlayChartDataPoint[];
        legendData: LegendData;
        axesLabels: ChartAxesLabels;
        size?: DataViewMetadataColumn;
        sizeRange: NumberRange;
        dataLabelsSettings: PointDataLabelsSettings;
        defaultDataPointColor?: string;
        showAllDataPoints?: boolean;
        hasDynamicSeries?: boolean;
        fillPoint?: boolean;
        colorBorder?: boolean;
        frameKeys: any[];
        allDataPoints?: PlayChartDataPoint[][];
        currentFrameIndex?: number;
        lastRenderedFrameIndex?: number;
        colorByCategory?: boolean;
        currentViewport?: IViewport;
    }

    interface PlayChartMeasureMetadata {
        idx: {
            x?: number;
            y?: number;
            size?: number;
        };
        cols: {
            x?: DataViewMetadataColumn;
            y?: DataViewMetadataColumn;
            size?: DataViewMetadataColumn;
        };
        axesLabels: ChartAxesLabels;
    }

    interface MouseCoordinates {
        x: number;
        y: number;
    }

    interface PlayObjectProperties {
        defaultDataPointColor?: string;
        showAllDataPoints?: boolean;
        colorByCategory: boolean;
        currentFrameIndex?: number;
    }

    export class PlayChart implements ICartesianVisual {
        private static PlayChartCircleTagName = 'circle';
        private static BubbleRadius = 3 * 2;
        public static DefaultBubbleOpacity = 0.85;
        public static DimmedBubbleOpacity = 0.3;
        public static StrokeDarkenColorValue = 255 * 0.25;
        // Chart Area and size range values as defined by PV charts
        private static AreaOf300By300Chart = 90000;
        private static MinSizeRange = 200;
        private static MaxSizeRange = 3000;
        private static ClassName = 'playChart';
        private static MainGraphicsContextClassName = 'mainGraphicsContext';
        private static DataLabelsContextClassName = 'dataLabelsContext';
        private static FrameDuration = 1000;
        private static FrameDurationFudge = 48; // animating the traceLine during play needs this to ensure we finish animating the line before the next frame plays
        private static SliderMarginLeft = 15;
        private static SliderMarginRight = 35;

        private static DotClasses: ClassAndSelector = {
            class: 'dot',
            selector: '.dot'
        };

        private svg: D3.Selection;
        private element: JQuery;
        private mainGraphicsContext: D3.Selection;
        private dataLabelsContext: D3.Selection;
        private clearCatcher: D3.Selection;
        private mainGraphicsG: D3.Selection;
        private currentViewport: IViewport;
        private lastRenderedViewport: IViewport;
        private style: IVisualStyle;
        private data: PlayChartData;
        private dataView: DataView;
        private host: IVisualHostServices;
        private margin: IMargin;
        private xAxisProperties: IAxisProperties;
        private yAxisProperties: IAxisProperties;
        private colors: IDataColorPalette;
        private options: VisualInitOptions;
        private interactivity: InteractivityOptions;
        private cartesianVisualHost: ICartesianVisualHost;
        private isInteractiveChart: boolean;
        private interactivityService: IInteractivityService;
        private categoryAxisProperties: DataViewObject;
        private valueAxisProperties: DataViewObject;
        private animator: IGenericAnimator;
        private isFrozen: boolean;
        private frameCount: number;
        private isPlaying: boolean;
        private playAxisContainer: JQuery; //contains the playButton and slider
        private playButton: JQuery;
        private slider: JQuery;
        private callout: JQuery;

        // do not call converter() when we call persistProperties and a new update() happens
        // NOTE: calling persistProperties will still cause a render() call to come from cartesianChart
        private ridiculousFlagForPersistProperties: boolean;

        constructor(options: PlayChartConstructorOptions) {
            this.isFrozen = false;
            if (options) {
                this.interactivityService = options.interactivityService;
                this.animator = options.animator;
                this.isFrozen = !!options.isFrozen;
            }
        }

        public init(options: CartesianVisualInitOptions) {
            this.options = options;
            let element = this.element = options.element;
            this.currentViewport = options.viewport;
            this.lastRenderedViewport = {
                height: this.currentViewport.height,
                width: this.currentViewport.width
            };
            this.style = options.style;
            this.host = options.host;
            this.colors = this.style.colorPalette.dataColors;
            this.interactivity = options.interactivity;
            this.cartesianVisualHost = options.cartesianHost;
            this.isInteractiveChart = options.interactivity && options.interactivity.isInteractiveLegend;

            element.addClass(PlayChart.ClassName);
            let svg = this.svg = options.svg;
            this.clearCatcher = this.svg.select(".clearCatcher");

            this.mainGraphicsG = svg.append('g')
                .classed(PlayChart.MainGraphicsContextClassName, true);

            this.mainGraphicsContext = this.mainGraphicsG.append('svg');
            this.dataLabelsContext = this.mainGraphicsG.append('g')
                .classed(PlayChart.DataLabelsContextClassName, true);
        }

        private static convertMatrixToCategorical(matrix: DataViewMatrix, frame: number): DataViewCategorical {
            var node = matrix.rows.root.children[frame];

            var categorical: DataViewCategorical = {
                categories: [],
                values: powerbi.data.DataViewTransform.createValueColumns()
            };

            let category: DataViewCategoryColumn = {
                source: matrix.rows.levels[1].sources[0],
                values: [],
                objects: undefined,
                identity: []
            };

            if (matrix.columns.levels.length > 1) {
                categorical.values.source = matrix.columns.levels[0].sources[0];
                //let leafCount = 0;
                let leafOfGroup = matrix.columns.root;
                while (leafOfGroup.children && leafOfGroup.children[0].children) {
                    leafOfGroup = leafOfGroup.children[0];
                }

                let nodeQueue = [];
                let columnNode = matrix.columns.root;
                let leafOffset = 0;
                while (columnNode) {
                    if (columnNode.children && columnNode.children[0].children) {
                        for (var j = 0, jlen = columnNode.children.length; j < jlen; j++) {
                            nodeQueue.push(columnNode.children[j]);
                        }
                    } else if (columnNode.children) {
                        let columnLength = columnNode.children.length;
                        for (let j = 0; j < columnLength; j++) {
                            let source = <any>_.create(matrix.valueSources[j], { groupName: columnNode.value });
                            let dataViewColumn: DataViewValueColumn = {
                                identity: columnNode.identity,
                                values: [],
                                source: source,
                                value: columnNode.value,
                            };
                            categorical.values.push(dataViewColumn);
                        }

                        for (let i = 0, len = node.children.length; i < len; i++) {
                            let innerNode = node.children[i];
                            category.identity.push(innerNode.identity);
                            category.values.push(innerNode.value);

                            for (let j = 0; j < columnLength; j++) {
                                categorical.values[j+leafOffset].values.push(innerNode.values[j+leafOffset].value);
                            }
                        }

                        leafOffset += columnLength;
                    }

                    if (nodeQueue.length > 0) {
                        columnNode = nodeQueue[0];
                        nodeQueue = nodeQueue.splice(1);
                    } else
                        columnNode = undefined;
                }
            } else {
                let columnLength = matrix.columns.root.children.length;
                for (let j = 0; j < columnLength; j++) {
                    let dataViewColumn: DataViewValueColumn = {
                        identity: undefined,
                        values: [],
                        source: matrix.valueSources[j]
                    };
                    categorical.values.push(dataViewColumn);
                }

                for (let i = 0, len = node.children.length; i < len; i++) {
                    let innerNode = node.children[i];
                    category.identity.push(innerNode.identity);
                    category.values.push(innerNode.value);

                    // v1, no series.
                    for (let j = 0; j < columnLength; j++) {
                        categorical.values[j].values.push(innerNode.values[j].value);
                    }
                }
            }
            categorical.categories.push(category);

            return categorical;
        }

        private static getObjectProperties(dataViewMetadata: DataViewMetadata, dataLabelsSettings?: PointDataLabelsSettings): PlayObjectProperties {
            let objectProperties: PlayObjectProperties = {
                colorByCategory: true, //default to true
            };
            if (dataViewMetadata && dataViewMetadata.objects) {
                var objects = dataViewMetadata.objects;

                objectProperties.defaultDataPointColor = DataViewObjects.getFillColor(objects, columnChartProps.dataPoint.defaultColor);
                objectProperties.showAllDataPoints = DataViewObjects.getValue<boolean>(objects, columnChartProps.dataPoint.showAllDataPoints);

                var labelsObj = objects['categoryLabels'];
                if (labelsObj && dataLabelsSettings) {
                    dataLabelsSettings.show = (labelsObj['show'] !== undefined) ? <boolean>labelsObj['show'] : dataLabelsSettings.show;
                    dataLabelsSettings.precision = (labelsObj['labelsPrecision'] !== undefined) ? +<string>labelsObj['labelsPrecision'] : dataLabelsSettings.precision;
                    if (labelsObj['color'] !== undefined) {
                        dataLabelsSettings.labelColor = (<Fill>labelsObj['color']).solid.color;
                    }
                }

                var generalObj = objects['general'];
                if (generalObj) {
                    if (generalObj['colorByCategory'] != null)
                        objectProperties.colorByCategory = !!generalObj['colorByCategory'];
                    if (generalObj['frozenIndex'] != null)
                        objectProperties.currentFrameIndex = <number>generalObj['frozenIndex'];
                }
            }
            return objectProperties;
        }

        public static converter(dataView: DataView, currentViewport: IViewport, colorPalette: IDataColorPalette, interactivityService?: IInteractivityService, categoryAxisProperties?: DataViewObject, valueAxisProperties?: DataViewObject): PlayChartData {
            var categoryValues: any[],
                categoryFormatter: IValueFormatter,
                categoryObjects: DataViewObjects[],
                categoryIdentities: DataViewScopeIdentity[];

            let dataViewCategorical: DataViewCategorical = dataView.categorical;
            if (!dataViewCategorical && dataView.matrix) {
                dataViewCategorical = PlayChart.convertMatrixToCategorical(dataView.matrix, dataView.matrix.rows.root.children.length - 1);
            }
            let dataViewMetadata: DataViewMetadata = dataView.metadata;

            if (dataViewCategorical.categories && dataViewCategorical.categories.length > 0) {
                categoryValues = dataViewCategorical.categories[0].values;
                categoryFormatter = valueFormatter.create({ format: valueFormatter.getFormatString(dataViewCategorical.categories[0].source, playChartProps.general.formatString), value: categoryValues[0], value2: categoryValues[categoryValues.length - 1] });
                categoryIdentities = dataViewCategorical.categories[0].identity;
                categoryObjects = dataViewCategorical.categories[0].objects;
            }
            else {
                categoryValues = [null];
                // creating default formatter for null value (to get the right string of empty value from the locale)
                categoryFormatter = valueFormatter.createDefaultFormatter(null);
            }

            let categories = dataViewCategorical.categories;
            let dataValues = dataViewCategorical.values;

            if (!dataValues)
                return;

            let hasDynamicSeries = !!dataValues.source;
            let grouped = dataValues.grouped();
            let dvSource = dataValues.source;
            let playMetadata = PlayChart.getMetadata(grouped, dvSource);
            let dataLabelsSettings = dataLabelUtils.getDefaultPointLabelSettings();
            let objectProperties = PlayChart.getObjectProperties(dataViewMetadata, dataLabelsSettings);

            let allDataPoints = [];
            let frameKeys = [];
            let dataPoints = undefined;
            let rowChildrenLength = dataView.matrix.rows.root.children.length;
            if (!dataView.categorical && dataView.matrix && rowChildrenLength > 0) {
                let keySourceColumn = dataView.matrix.rows.levels[0].sources[0];
                let keyFormatter = valueFormatter.create({
                    format: valueFormatter.getFormatString(keySourceColumn, playChartProps.general.formatString),
                    value: dataView.matrix.rows.root.children[0],
                    value2: dataView.matrix.rows.root.children[rowChildrenLength-1],
                });
                for (let i = 0, len = rowChildrenLength; i < len; i++) {
                    var key = dataView.matrix.rows.root.children[i];
                    frameKeys.push(keyFormatter.format(key.value));

                    dataViewCategorical = PlayChart.convertMatrixToCategorical(dataView.matrix, i);
                    categoryValues = dataViewCategorical.categories[0].values;
                    categoryFormatter = valueFormatter.create({
                        format: valueFormatter.getFormatString(dataViewCategorical.categories[0].source, playChartProps.general.formatString),
                        value: categoryValues[0],
                        value2: categoryValues[categoryValues.length - 1],
                    });
                    categoryIdentities = dataViewCategorical.categories[0].identity;
                    categoryObjects = dataViewCategorical.categories[0].objects;

                    categories = dataViewCategorical.categories;
                    dataValues = dataViewCategorical.values;

                    dataPoints = PlayChart.createDataPoints(
                        dataValues,
                        playMetadata,
                        categories,
                        categoryValues,
                        categoryFormatter,
                        categoryIdentities,
                        categoryObjects,
                        colorPalette,
                        currentViewport,
                        hasDynamicSeries,
                        dataLabelsSettings,
                        objectProperties.defaultDataPointColor,
                        objectProperties.colorByCategory);
                    allDataPoints.push(dataPoints);
                }
            }
            else {
                dataPoints = PlayChart.createDataPoints(
                    dataValues,
                    playMetadata,
                    categories,
                    categoryValues,
                    categoryFormatter,
                    categoryIdentities,
                    categoryObjects,
                    colorPalette,
                    currentViewport,
                    hasDynamicSeries,
                    dataLabelsSettings,
                    objectProperties.defaultDataPointColor,
                    objectProperties.colorByCategory);
            }

            // use the saved frame index, or default to the last frame
            if (objectProperties.currentFrameIndex == null) {
                objectProperties.currentFrameIndex = frameKeys.length - 1; //default
                // current dataPoints is already the last frame
            }
            else if (objectProperties.currentFrameIndex < frameKeys.length) {
                dataPoints = allDataPoints[objectProperties.currentFrameIndex];
            }

            if (interactivityService) {
                interactivityService.applySelectionStateToData(dataPoints);
            }

            let legendItems = hasDynamicSeries
                ? PlayChart.createSeriesLegend(dataValues, colorPalette, dataValues,
                    valueFormatter.getFormatString(dvSource, playChartProps.general.formatString),
                    objectProperties.defaultDataPointColor)
                : [];

            let legendTitle = dataValues && dvSource ? dvSource.displayName : "";
            if (!legendTitle) {
                legendTitle = categories && categories[0].source.displayName ? categories[0].source.displayName : "";
            }

            let legendData = { title: legendTitle, dataPoints: legendItems };
            let sizeRange = PlayChart.getSizeRangeForGroups(grouped, playMetadata.idx.size);

            if (categoryAxisProperties && categoryAxisProperties["showAxisTitle"] !== null && categoryAxisProperties["showAxisTitle"] === false) {
                playMetadata.axesLabels.x = null;
            }

            if (valueAxisProperties && valueAxisProperties["showAxisTitle"] !== null && valueAxisProperties["showAxisTitle"] === false) {
                playMetadata.axesLabels.y = null;
            }

            return {
                xCol: playMetadata.cols.x,
                yCol: playMetadata.cols.y,
                dataPoints: dataPoints,
                legendData: legendData,
                axesLabels: playMetadata.axesLabels,
                hasSelection: false,
                selectedIds: [],
                size: playMetadata.cols.size,
                sizeRange: sizeRange,
                dataLabelsSettings: dataLabelsSettings,
                defaultDataPointColor: objectProperties.defaultDataPointColor,
                hasDynamicSeries: hasDynamicSeries,
                showAllDataPoints: objectProperties.showAllDataPoints,
                allDataPoints: allDataPoints,
                frameKeys: frameKeys,
                colorByCategory: objectProperties.colorByCategory,
                currentFrameIndex: objectProperties.currentFrameIndex,
            };
        }

        private static getSizeRangeForGroups(
            dataViewValueGroups: DataViewValueColumnGroup[],
            sizeColumnIndex: number): NumberRange {

            let result: NumberRange = {};
            if (dataViewValueGroups) {
                dataViewValueGroups.forEach((group) => {
                    let sizeColumn = PlayChart.getMeasureValue(sizeColumnIndex, group.values);
                    let currentRange: NumberRange = AxisHelper.getRangeForColumn(sizeColumn);
                    if (result.min == null || result.min > currentRange.min) {
                        result.min = currentRange.min;
                    }
                    if (result.max == null || result.max < currentRange.max) {
                        result.max = currentRange.max;
                    }
                });
            }
            return result;
        }

        private static createDataPoints(
            dataValues: DataViewValueColumns,
            metadata: PlayChartMeasureMetadata,
            categories: DataViewCategoryColumn[],
            categoryValues: any[],
            categoryFormatter: IValueFormatter,
            categoryIdentities: DataViewScopeIdentity[],
            categoryObjects: DataViewObjects[],
            colorPalette: IDataColorPalette,
            viewport: IViewport,
            hasDynamicSeries: boolean,
            labelSettings: PointDataLabelsSettings,
            defaultDataPointColor?: string,
            colorByCategory?: boolean): PlayChartDataPoint[]{

            let dataPoints: PlayChartDataPoint[] = [],
                indicies = metadata.idx,
                formatStringProp = playChartProps.general.formatString,
                dataValueSource = dataValues.source,
                grouped = dataValues.grouped();

            let colorHelper = new ColorHelper(colorPalette, playChartProps.dataPoint.fill, defaultDataPointColor);

            for (let categoryIdx = 0, ilen = categoryValues.length; categoryIdx < ilen; categoryIdx++) {
                let categoryValue = categoryValues[categoryIdx];

                for (let seriesIdx = 0, len = grouped.length; seriesIdx < len; seriesIdx++) {
                    let grouping = grouped[seriesIdx];
                    let seriesValues = grouping.values;
                    let measureX = PlayChart.getMeasureValue(indicies.x, seriesValues);
                    let measureY = PlayChart.getMeasureValue(indicies.y, seriesValues);
                    let measureSize = PlayChart.getMeasureValue(indicies.size, seriesValues);

                    let xVal = measureX && measureX.values ? measureX.values[categoryIdx] : null;
                    let yVal = measureY && measureY.values ? measureY.values[categoryIdx] : 0;
                    let size = measureSize && measureSize.values ? measureSize.values[categoryIdx] : null;

                    let hasNullValue = (xVal == null) || (yVal == null);
                    if (hasNullValue)
                        continue;

                    let color: string;
                    if (hasDynamicSeries) {
                        color = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);
                    }
                    else if (colorByCategory) {
                        color = colorHelper.getColorForSeriesValue(categoryObjects && categoryObjects[categoryIdx], dataValues.identityFields, categoryValue);
                    }
                    else {
                        // If we have no Size measure then use a blank query name
                        let measureSource = (measureSize != null)
                            ? measureSize.source.queryName
                            : '';

                        color = colorHelper.getColorForMeasure(categoryObjects && categoryObjects[categoryIdx], measureSource);
                    }

                    let category = categories && categories.length > 0 ? categories[0] : null;
                    let identity = SelectionIdBuilder.builder()
                        .withCategory(category, categoryIdx)
                        .withSeries(dataValues, grouping)
                        .createSelectionId();

                    let seriesData: TooltipSeriesDataItem[] = [];
                    if (dataValueSource) {
                        // Dynamic series
                        seriesData.push({ value: grouping.name, metadata: { source: dataValueSource, values: [] } });
                    }
                    if (measureX) {
                        seriesData.push({ value: xVal, metadata: measureX });
                    }
                    if (measureY) {
                        seriesData.push({ value: yVal, metadata: measureY });
                    }
                    if (measureSize && measureSize.values && measureSize.values.length > 0) {
                        seriesData.push({ value: measureSize.values[categoryIdx], metadata: measureSize });
                    }

                    let tooltipInfo: TooltipDataItem[] = TooltipBuilder.createTooltipInfo(formatStringProp, null, categoryValue, null, categories, seriesData);

                    let dataPoint: PlayChartDataPoint = {
                        x: xVal,
                        y: yVal,
                        size: size,
                        radius: { sizeMeasure: measureSize, index: categoryIdx },
                        fill: color,
                        category: categoryFormatter.format(categoryValue),
                        selected: false,
                        identity: identity,
                        tooltipInfo: tooltipInfo,
                        labelFill: labelSettings.labelColor,
                    };

                    dataPoints.push(dataPoint);
                }
            }
            // TODO: is it more efficient to calculate min/max for the frame here and pass back {dp, min, max}?
            return dataPoints;
        }

        private static createSeriesLegend(
            dataValues: DataViewValueColumns,
            colorPalette: IDataColorPalette,
            categorical: DataViewValueColumns,
            formatString: string,
            defaultDataPointColor: string): LegendDataPoint[] {

            let grouped = dataValues.grouped();
            let colorHelper = new ColorHelper(colorPalette, playChartProps.dataPoint.fill, defaultDataPointColor);

            let legendItems: LegendDataPoint[] = [];
            for (let i = 0, len = grouped.length; i < len; i++) {
                let grouping = grouped[i];
                let color = colorHelper.getColorForSeriesValue(grouping.objects, dataValues.identityFields, grouping.name);
                legendItems.push({
                    color: color,
                    icon: LegendIcon.Circle,
                    label: valueFormatter.format(grouping.name, formatString),
                    identity: grouping.identity ? SelectionId.createWithId(grouping.identity) : SelectionId.createNull(),
                    selected: false,
                });
            }

            return legendItems;
        }

        public static getBubbleRadius(radiusData: RadiusData, sizeRange: NumberRange, viewPort: IViewport): number {
            let actualSizeDataRange = null;
            let bubblePixelAreaSizeRange = null;
            let measureSize = radiusData.sizeMeasure;

            if (!measureSize)
                return PlayChart.BubbleRadius;

            let minSize = sizeRange.min ? sizeRange.min : 0;
            let maxSize = sizeRange.max ? sizeRange.max : 0;

            let min = Math.min(minSize, 0);
            let max = Math.max(maxSize, 0);
            actualSizeDataRange = {
                minRange: min,
                maxRange: max,
                delta: max - min
            };

            bubblePixelAreaSizeRange = PlayChart.getBubblePixelAreaSizeRange(viewPort, PlayChart.MinSizeRange, PlayChart.MaxSizeRange);

            if (measureSize.values) {
                let sizeValue = measureSize.values[radiusData.index];
                if (sizeValue != null) {
                    return PlayChart.projectSizeToPixels(sizeValue, actualSizeDataRange, bubblePixelAreaSizeRange) / 2;
                }
            }

            return PlayChart.BubbleRadius;
        }

        public static getMeasureValue(measureIndex: number, seriesValues: DataViewValueColumn[]): DataViewValueColumn {
            if (measureIndex >= 0)
                return seriesValues[measureIndex];

            return null;
        }

        private static getMetadata(grouped: DataViewValueColumnGroup[], source: DataViewMetadataColumn): PlayChartMeasureMetadata {
            let xIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, 'X');
            let yIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, 'Y');
            let sizeIndex = DataRoleHelper.getMeasureIndexOfRole(grouped, 'Size');
            let xCol: DataViewMetadataColumn;
            let yCol: DataViewMetadataColumn;
            let sizeCol: DataViewMetadataColumn;
            let xAxisLabel = "";
            let yAxisLabel = "";

            if (grouped && grouped.length) {
                let firstGroup = grouped[0],
                    measureCount = firstGroup.values.length;

                if (!(xIndex >= 0))
                    xIndex = PlayChart.getDefaultMeasureIndex(measureCount, yIndex, sizeIndex);
                if (!(yIndex >= 0))
                    yIndex = PlayChart.getDefaultMeasureIndex(measureCount, xIndex, sizeIndex);
                if (!(sizeIndex >= 0))
                    sizeIndex = PlayChart.getDefaultMeasureIndex(measureCount, xIndex, yIndex);

                if (xIndex >= 0) {
                    xCol = firstGroup.values[xIndex].source;
                    xAxisLabel = firstGroup.values[xIndex].source.displayName;
                }
                if (yIndex >= 0) {
                    yCol = firstGroup.values[yIndex].source;
                    yAxisLabel = firstGroup.values[yIndex].source.displayName;
                }
                if (sizeIndex >= 0) {
                    sizeCol = firstGroup.values[sizeIndex].source;
                }
            }

            return {
                idx: {
                    x: xIndex,
                    y: yIndex,
                    size: sizeIndex,
                },
                cols: {
                    x: xCol,
                    y: yCol,
                    size: sizeCol,
                },
                axesLabels: {
                    x: xAxisLabel,
                    y: yAxisLabel
                }
            };
        }

        private static getDefaultMeasureIndex(count: number, usedIndex: number, usedIndex2: number): number {
            for (let i = 0; i < count; i++) {
                if (i !== usedIndex && i !== usedIndex2)
                    return i;
            }
        }

        public setData(dataViews: DataView[]) {
            let defaultData = {
                xCol: undefined,
                yCol: undefined,
                dataPoints: [],
                legendData: { dataPoints: [] },
                axesLabels: { x: '', y: '' },
                selectedIds: [],
                sizeRange: [],
                dataLabelsSettings: dataLabelUtils.getDefaultPointLabelSettings(),
                defaultDataPointColor: null,
                hasDynamicSeries: false,
                frameKeys: [],
            };

            if (dataViews.length > 0 && dataViews[0] != null) {
                let dataView = dataViews[0];

                this.categoryAxisProperties = CartesianHelper.getCategoryAxisProperties(dataView.metadata, true);
                this.valueAxisProperties = CartesianHelper.getValueAxisProperties(dataView.metadata, true);
                this.dataView = dataView;

                if (this.ridiculousFlagForPersistProperties && dataView.metadata) {
                    let objectProps = PlayChart.getObjectProperties(dataView.metadata);
                    // only copy frameIndex since it is the only property using persistProperties
                    if (this.data) {
                        this.data.currentFrameIndex = objectProps.currentFrameIndex;
                    }
                    // turn off the flag that was set by our persistProperties call
                    this.ridiculousFlagForPersistProperties = false;
                }
                else if (dataView.matrix || dataView.categorical) {
                    let data = PlayChart.converter(dataView, this.currentViewport, this.colors, this.interactivityService, this.categoryAxisProperties, this.valueAxisProperties);
                    if (data) {
                        this.data = data;
                        if (this.interactivityService && this.data.dataPoints) {
                            this.interactivityService.applySelectionStateToData(this.data.dataPoints);
                        }
                    }
                    else
                        this.data = defaultData;
                }
                else
                    this.data = defaultData;
            }
            else {
                this.data = defaultData;
            }
        }

        public calculateLegend(): LegendData {
            return this.data.legendData;
        }

        public hasLegend(): boolean {
            return this.data && this.data.hasDynamicSeries;
        }

        public enumerateObjectInstances(enumeration: ObjectEnumerationBuilder, options: EnumerateVisualObjectInstancesOptions): void {
            switch (options.objectName) {
                case 'general':
                    enumeration.pushInstance({
                        selector: null,
                        properties: {
                            colorByCategory: this.data ? this.data.colorByCategory : true,
                        },
                        objectName: 'general'
                    });
                    break;
                // NOT WORKING RIGHT NOW
                case 'dataPoint - butNotWorkingAndLeavingHereForTSLINT':
                    let categoricalDataView: DataViewCategorical = this.dataView && this.dataView.categorical ? this.dataView.categorical : null;
                    if (!GradientUtils.hasGradientRole(categoricalDataView))
                        return this.enumerateDataPoints(enumeration);
                    break;
                case 'categoryAxis':
                    enumeration.pushInstance({
                        selector: null,
                        properties: {
                            showAxisTitle: !this.categoryAxisProperties || this.categoryAxisProperties["showAxisTitle"] == null ? true : this.categoryAxisProperties["showAxisTitle"]
                        },
                        objectName: 'categoryAxis'
                    });
                    break;
                case 'valueAxis':
                    enumeration.pushInstance({
                        selector: null,
                        properties: {
                            showAxisTitle: !this.valueAxisProperties || this.valueAxisProperties["showAxisTitle"] == null ? true : this.valueAxisProperties["showAxisTitle"]
                        },
                        objectName: 'valueAxis'
                    });
                    break;
                case 'categoryLabels':
                    if (this.data)
                        dataLabelUtils.enumerateCategoryLabels(enumeration, this.data.dataLabelsSettings, true);
                    else
                        dataLabelUtils.enumerateCategoryLabels(enumeration, null, true);
                    break;
                case 'fillPoint':
                    // Check if the card should be shown or not based on the existance of size measure
                    if (this.hasSizeMeasure())
                        return;

                    enumeration.pushInstance({
                        objectName: 'fillPoint',
                        selector: null,
                        properties: {
                            show: this.data.fillPoint,
                        },
                    });
                    break;
                case 'colorBorder':
                    // Check if the card should be shown or not based on the existance of size measure
                    if (this.hasSizeMeasure())
                        enumeration.pushInstance({
                            objectName: 'colorBorder',
                            selector: null,
                            properties: {
                                show: this.data.colorBorder,
                            },
                        });
                    break;
            }
        }

        private hasSizeMeasure(): boolean {
            let sizeRange = this.data.sizeRange;
            return sizeRange && sizeRange.min !== undefined;
        }

        private enumerateDataPoints(enumeration: ObjectEnumerationBuilder): void {
            let data = this.data;
            if (!data)
                return;

            let seriesCount = data.dataPoints.length;

            if (!data.hasDynamicSeries) {
                // Add default color and show all slices
                enumeration.pushInstance({
                    objectName: 'dataPoint',
                    selector: null,
                    properties: {
                        defaultColor: { solid: { color: data.defaultDataPointColor || this.colors.getColorByIndex(0).value } },
                    }
                }).pushInstance({
                    objectName: 'dataPoint',
                    selector: null,
                    properties: {
                        showAllDataPoints: !!data.showAllDataPoints
                    }
                });

                for (let i = 0; i < seriesCount; i++) {
                    let seriesDataPoints = data.dataPoints[i];
                    enumeration.pushInstance({
                        objectName: 'dataPoint',
                        displayName: seriesDataPoints.category,
                        selector: ColorHelper.normalizeSelector(seriesDataPoints.identity.getSelector(), /*isSingleSeries*/ true),
                        properties: {
                            fill: { solid: { color: seriesDataPoints.fill } }
                        },
                    });
                }
            }
            else {
                let legendDataPointLength = data.legendData.dataPoints.length;
                for (let i = 0; i < legendDataPointLength; i++) {
                    let series = data.legendData.dataPoints[i];
                    enumeration.pushInstance({
                        objectName: 'dataPoint',
                        displayName: series.label,
                        selector: ColorHelper.normalizeSelector(series.identity.getSelector()),
                        properties: {
                            fill: { solid: { color: series.color } },
                        },
                    });
                }
            }
        }

        public calculateAxesProperties(options: CalculateScaleAndDomainOptions): IAxisProperties[] {
            let data = this.data;
            let dataPoints = data.dataPoints;
            let viewport = this.currentViewport = options.viewport;
            let margin = options.margin;

            this.currentViewport = viewport;
            this.margin = margin;

            if (this.data) {
                this.data.currentViewport = {
                    height: viewport.height,
                    width: viewport.width
                };
            }

            let width = viewport.width - (margin.left + margin.right);
            let height = viewport.height - (margin.top + margin.bottom);
            let minY = 0,
                maxY = 10,
                minX = 0,
                maxX = 10;
            if (dataPoints.length > 0) {
                if (data.allDataPoints && data.allDataPoints.length > 0) {
                    minY = minX = Number.MAX_VALUE;
                    maxY = maxX = Number.MIN_VALUE;
                    for (let i = 0, len = data.allDataPoints.length; i < len; i++) {
                        let dps = data.allDataPoints[i];
                        minY = Math.min(d3.min<PlayChartDataPoint, number>(dps, d => d.y), minY);
                        maxY = Math.max(d3.max<PlayChartDataPoint, number>(dps, d => d.y), maxY);
                        minX = Math.min(d3.min<PlayChartDataPoint, number>(dps, d => d.x), minX);
                        maxX = Math.max(d3.max<PlayChartDataPoint, number>(dps, d => d.x), maxX);
                    }
                } else {
                    minY = d3.min<PlayChartDataPoint, number>(dataPoints, d => d.y);
                    maxY = d3.max<PlayChartDataPoint, number>(dataPoints, d => d.y);
                    minX = d3.min<PlayChartDataPoint, number>(dataPoints, d => d.x);
                    maxX = d3.max<PlayChartDataPoint, number>(dataPoints, d => d.x);
                }
            }

            let xDomain = [minX, maxX];
            let combinedXDomain = AxisHelper.combineDomain(options.forcedXDomain, xDomain);

            this.xAxisProperties = AxisHelper.createAxis({
                pixelSpan: width,
                dataDomain: combinedXDomain,
                metaDataColumn: data.xCol,
                formatStringProp: playChartProps.general.formatString,
                outerPadding: 0,
                isScalar: true,
                isVertical: false,
                forcedTickCount: options.forcedTickCount,
                useTickIntervalForDisplayUnits: true,
                isCategoryAxis: true,//Play doesn't have a categorical axis, but this is needed for the pane to react correctly to the x-axis toggle one/off
                scaleType: options.categoryAxisScaleType,
            });
            this.xAxisProperties.axis.tickSize(-height, 0);
            this.xAxisProperties.axisLabel = this.data.axesLabels.x;

            let combinedDomain = AxisHelper.combineDomain(options.forcedYDomain, [minY, maxY]);

            this.yAxisProperties = AxisHelper.createAxis({
                pixelSpan: height,
                dataDomain: combinedDomain,
                metaDataColumn: data.yCol,
                formatStringProp: playChartProps.general.formatString,
                outerPadding: 0,
                isScalar: true,
                isVertical: true,
                forcedTickCount: options.forcedTickCount,
                useTickIntervalForDisplayUnits: true,
                isCategoryAxis: false,
                scaleType: options.valueAxisScaleType
            });
            this.yAxisProperties.axisLabel = this.data.axesLabels.y;

            return [this.xAxisProperties, this.yAxisProperties];
        }

        public overrideXScale(xProperties: IAxisProperties): void {
            this.xAxisProperties = xProperties;
        }

        private createSliderDOM(): void {
            // a container to position the button and the slider together
            this.playAxisContainer = $('<div></div>')
                .css('position', 'absolute')
                .css('left', '15px')
                .css('bottom', '35px')
                .appendTo(this.element);

            var playButtonCircle = $('<div class="button-container"></div>')
                .appendTo(this.playAxisContainer)
                .on('click', () => {
                    this.play();
                });
            this.playButton = $('<div class="play"></div>')
                .appendTo(playButtonCircle);

            // the noUiSlider
            this.slider = $('<div class="sliders"></div>')
                .appendTo(this.playAxisContainer);
        }

        private createSliderControl(slider: any, sliderWidth: number): void {
            // re-create the slider
            if ((<any>slider[0]).noUiSlider)
                (<any>slider[0]).noUiSlider.destroy();

            var numFrames = this.data.frameKeys.length;
            if (numFrames > 0) {
                var filterPipLabels = this.createPipsFilterFn(sliderWidth);
                var lastIndex = numFrames - 1;
                noUiSlider.create(
                    slider[0],
                    {
                        step: 1,
                        start: [this.data.currentFrameIndex],
                        range: {
                            min: [0],
                            max: [lastIndex],
                        },
                        pips: {
                            mode: 'steps',
                            density: 12,
                            format: {
                                to: (index) => this.data.frameKeys[index],
                                from: (value) => this.data.frameKeys.indexOf(value),
                            },
                            filter: filterPipLabels,
                        },
                    });

                (<any>slider[0]).noUiSlider.on('slide', () => {
                    this.isPlaying = false; //stop the play sequence
                    var indexToShow = Math.round((<any>slider[0]).noUiSlider.get());
                    if (indexToShow >= 0 && indexToShow !== this.data.currentFrameIndex && indexToShow < this.data.allDataPoints.length) {
                        this.data.currentFrameIndex = indexToShow;
                        var data = this.data.allDataPoints[indexToShow];
                        this.data.dataPoints = data;
                        this.persistFrameIndex(indexToShow); //this will cause a render
                    }
                });
            }
            else {
                noUiSlider.create(
                    slider[0],
                    {
                        step: 1,
                        start: [0],
                        range: {
                            min: [0],
                            max: [0],
                        },
                    });
            }
        }

        private createPipsFilterFn(sliderWidth: number): any {
            let textProperties: TextProperties = {
                fontFamily: 'wf_segoe-ui_normal',
                fontSize: jsCommon.PixelConverter.toString(14),
            };
            let maxLabelWidth = 0;
            let anyWillWordBreak = false;
            for (var key of this.data.frameKeys) {
                maxLabelWidth = Math.max(maxLabelWidth, jsCommon.WordBreaker.getMaxWordWidth(key + "", TextMeasurementService.measureSvgTextWidth, textProperties));
                anyWillWordBreak = anyWillWordBreak || jsCommon.WordBreaker.hasBreakers(key);
            }

            var pipSize = 1; //0=hide, 1=large, 2=small
            var skipMod = 1;
            var maxAllowedLabelWidth = this.frameCount > 1 ? sliderWidth / (this.frameCount - 1) : sliderWidth;
            var widthRatio = maxLabelWidth / maxAllowedLabelWidth;

            if (widthRatio > 1.25) {
                skipMod = Math.ceil(widthRatio);
                pipSize = 2;
            }
            else if (widthRatio > 1.0 || anyWillWordBreak) {
                // wordbreak line wrapping is automatic, and we don't reserve enough space to show two lines of text with the larger font
                pipSize = 2;
            }

            var filterPipLabels = (index: any, type: any) => {
                // noUiSlider will word break / wrap to new lines, so max width is the max word length
                if (index % skipMod === 0) {
                    return pipSize;
                }
                return 0; //hide
            };

            return filterPipLabels;
        }

        public render(suppressAnimations: boolean): CartesianVisualRenderResult {
            if (!this.data || !this.dataView)
                return { dataPoints: [], behaviorOptions: null, labelDataPoints: [] };

            let data = this.data;
            let dataPoints = this.data.dataPoints;

            let margin = this.margin;
            let viewport = this.currentViewport;
            let width = viewport.width - (margin.left + margin.right);
            let height = viewport.height - (margin.top + margin.bottom);
            let xScale = this.xAxisProperties.scale;
            let yScale = this.yAxisProperties.scale;

            var hasSelection = false;
            if (this.interactivityService) {
                this.interactivityService.applySelectionStateToData(dataPoints);
                hasSelection = this.interactivityService.hasSelection();
            }

            this.mainGraphicsContext
                .attr('width', width)
                .attr('height', height);

            let sortedData = dataPoints.sort(function (a, b) {
                return b.radius.sizeMeasure ? (b.radius.sizeMeasure.values[b.radius.index] - a.radius.sizeMeasure.values[a.radius.index]) : 0;
            });

            var playMarkers = this.drawPlayMarkers(sortedData, hasSelection, data.sizeRange, suppressAnimations);
            var labelsSelection: D3.Selection;
            if (this.data.dataLabelsSettings.show) {
                var layout = dataLabelUtils.getScatterChartLabelLayout(xScale, yScale, this.data.dataLabelsSettings, viewport, data.sizeRange);
                labelsSelection = dataLabelUtils.drawDefaultLabelsForDataPointChart(
                    dataPoints,
                    this.dataLabelsContext,
                    layout,
                    this.currentViewport,
                    !!this.animator && !suppressAnimations,
                    PlayChart.FrameDuration,
                    hasSelection);
            }
            else {
                dataLabelUtils.cleanDataLabels(this.dataLabelsContext);
            }

            TooltipManager.addTooltip(playMarkers, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);

            if (this.data) {
                if (!this.playAxisContainer && !this.isFrozen) {
                    this.createSliderDOM();
                }
                let frameKeys = this.data.frameKeys;
                let currentFrameIndex = this.data.currentFrameIndex;

                // callout / currentFrameIndex label
                if (!this.callout) {
                    this.callout = $('<span class="callout"></span>').appendTo(this.element);
                }

                var slider = this.slider;
                var sliderWidth = (width - PlayChart.SliderMarginLeft - PlayChart.SliderMarginRight);
                if (slider)
                    slider.css('width', sliderWidth + 'px');

                // TODO: make this smarter
                let resized = this.currentViewport.width !== this.lastRenderedViewport.width
                    || this.currentViewport.height !== this.lastRenderedViewport.height;
                let changed = !this.playAxisContainer
                    || frameKeys.length !== this.frameCount
                    || resized;

                // default to last frame if frameKeys have changed and it's not the first time we are rendering
                if (this.frameCount && (frameKeys.length !== this.frameCount || currentFrameIndex >= frameKeys.length))
                    currentFrameIndex = frameKeys.length - 1;

                if (changed && slider) {
                    this.frameCount = frameKeys.length;
                    this.createSliderControl(slider, sliderWidth);
                }

                // update callout to current frame index
                var calloutDimension = Math.min(height, width * 1.3); //compensate for tall and narrow-width viewport
                var fontSize = Math.max(12, Math.round(calloutDimension / 7)) + 'px';
                if (currentFrameIndex < frameKeys.length && currentFrameIndex >= 0) {
                    this.callout
                        .text(frameKeys[currentFrameIndex])
                        .css('font-size', fontSize);
                }
                else {
                    this.callout.text('');
                }

                if (slider) {
                    // ensure slider position
                    (<any>slider[0]).noUiSlider.set([currentFrameIndex]);
                }

                this.lastRenderedViewport = {
                    height: this.currentViewport.height,
                    width: this.currentViewport.width
                };
            }

            if (this.interactivityService) {
                var behaviorOptions: PlayBehaviorOptions = {
                    host: this.cartesianVisualHost,
                    root: this.svg,
                    dataPointsSelection: playMarkers,
                    labelsSelection: labelsSelection,
                    mainContext: this.mainGraphicsContext,
                    data: this.data,
                    visualInitOptions: this.options,
                    xAxisProperties: this.xAxisProperties,
                    yAxisProperties: this.yAxisProperties,
                    background: d3.select(this.element.get(0)),
                    clearCatcher: this.clearCatcher,
                    dataViewCat: this.dataView.categorical,
                    svg: this.mainGraphicsContext,
                    dataView: this.dataView,
                    renderTraceLine: PlayChart.renderTraceLine,
                };

                if (hasSelection) {
                    let flatAllDataPoints = _.flatten<SelectableDataPoint>(this.data.allDataPoints);
                    let uniqueDataPoints = _.uniq(flatAllDataPoints, (d: PlayChartDataPoint) => d.identity.getKey());
                    PlayChart.renderTraceLine(behaviorOptions, _.filter(uniqueDataPoints, 'selected'), !suppressAnimations);
                }
            }

            SVGUtil.flushAllD3TransitionsIfNeeded(this.options);

            // pass allDataPoints to keep track of current selected bubble even if it drops out for a few frames
            return { dataPoints: _.flatten<SelectableDataPoint>(this.data.allDataPoints), behaviorOptions: behaviorOptions, labelDataPoints: [] };
        }

        public static renderTraceLine(options: PlayBehaviorOptions, selectedPoints: SelectableDataPoint[], shouldAnimate: boolean): void {
            var seriesPoints: PlayChartDataPoint[][] = [];

            if (selectedPoints && selectedPoints.length > 0) {
                let currentFrameIndex = options.data.currentFrameIndex;
                let lastRenderedFrameIndex = options.data.lastRenderedFrameIndex;

                // filter to the selected identity, only up to and including the current frame. Add frames during play.
                let hasBubbleAtCurrentFrame = [];
                for (var selectedIndex = 0, selectedLen = selectedPoints.length; selectedIndex < selectedLen; selectedIndex++) {
                    seriesPoints[selectedIndex] = [];
                    hasBubbleAtCurrentFrame[selectedIndex] = false;
                    for (let frameIndex = 0, frameLen = options.data.allDataPoints.length; frameIndex < frameLen && frameIndex <= currentFrameIndex; frameIndex++) {
                        let values = options.data.allDataPoints[frameIndex].filter((value, index) => {
                            return value.identity.getKey() === selectedPoints[selectedIndex].identity.getKey();
                        });
                        if (values && values.length > 0) {
                            seriesPoints[selectedIndex].push(values[0]);
                            if (frameIndex === currentFrameIndex)
                                hasBubbleAtCurrentFrame[selectedIndex] = true;
                        }
                    }
                }
                if (seriesPoints.length > 0) {
                    let xScale = options.xAxisProperties.scale;
                    let yScale = options.yAxisProperties.scale;

                    let line = d3.svg.line()
                        .x((d: PlayChartDataPoint) => {
                            return xScale(d.x);
                        })
                        .y((d: PlayChartDataPoint) => {
                            return yScale(d.y);
                        })
                        .defined((d: PlayChartDataPoint) => {
                            return d.x !== null && d.y !== null;
                        });

                    // Render Lines
                    let traceLines = options.svg.selectAll('.traceLine').data(selectedPoints, (sp: PlayChartDataPoint) => sp.identity.getKey());
                    traceLines.enter()
                        .append('path')
                        .classed('traceLine', true);
                    // prepare array of new/previous lengths
                    // NOTE: can't use lambda because we need the "this" context to be the DOM Element associated with the .each()
                    let previousLengths = [], newLengths = [];
                    traceLines.each(function (d, i) {
                        let existingPath = (<SVGPathElement>this);
                        let previousLength = existingPath.hasAttribute('d') ? existingPath.getTotalLength() : 0;
                        previousLengths.push(previousLength);
                        // create offline SVG for new path measurement
                        let tempSvgPath = $('<svg><path></path></svg>');
                        let tempPath = $('path', tempSvgPath);
                        tempPath.attr('d', line(seriesPoints[i]));
                        let newLength = (<SVGPathElement>tempPath.get()[0]).getTotalLength();
                        newLengths.push(newLength);
                    });
                    // animate using stroke-dash* trick
                    if (lastRenderedFrameIndex == null || currentFrameIndex >= lastRenderedFrameIndex) {
                        // growing line
                        traceLines
                            .style('stroke', (d: PlayChartDataPoint) => PlayChart.getStrokeFill(d, true))
                            .attr({
                                'd': (d, i: number) => {
                                    return line(seriesPoints[i]);
                                },
                                'stroke-dasharray': (d, i) => newLengths[i] + " " + newLengths[i],
                                'stroke-dashoffset': (d, i) => newLengths[i] - previousLengths[i],
                            });
                        if (shouldAnimate) {
                            traceLines
                                .transition()
                                .duration(PlayChart.FrameDuration - PlayChart.FrameDurationFudge)
                                .attr('stroke-dashoffset', 0);
                        }
                        else {
                            traceLines.attr('stroke-dashoffset', 0);
                        }
                    }
                    else {
                        // shrinking line
                        if (shouldAnimate) {
                            traceLines
                                .transition()
                                .duration(PlayChart.FrameDuration - PlayChart.FrameDurationFudge)
                                .attr('stroke-dashoffset', (d, i) => previousLengths[i] - newLengths[i])
                                .transition()
                                .duration(1) // animate the shrink first, then update with new line properties
                                .delay(PlayChart.FrameDuration - PlayChart.FrameDurationFudge)
                                .style('stroke', (d: PlayChartDataPoint) => PlayChart.getStrokeFill(d, true))
                                .attr({
                                    'd': (d, i) => {
                                        return line(seriesPoints[i]);
                                    },
                                    'stroke-dasharray': (d, i) => newLengths[i] + " " + newLengths[i],
                                    'stroke-dashoffset': 0,
                                });
                        }
                        else {
                            traceLines
                                .style('stroke', (d: PlayChartDataPoint) => PlayChart.getStrokeFill(d, true))
                                .attr({
                                    'd': (d, i) => {
                                        return line(seriesPoints[i]);
                                    },
                                    'stroke-dasharray': (d, i) => newLengths[i] + " " + newLengths[i],
                                    'stroke-dashoffset': 0,
                                });
                        }
                    }
                    traceLines.exit()
                        .remove();

                    // Render circles
                    // slice to length-1 because we draw lines to the current bubble but we don't need to draw the current frame's bubble
                    let circlePoints: PlayChartDataPoint[] = [];
                    for (var selectedIndex = 0; selectedIndex < seriesPoints.length; selectedIndex++) {
                        let points = seriesPoints[selectedIndex];
                        let newPoints = hasBubbleAtCurrentFrame[selectedIndex] ? points.slice(0, points.length - 1) : points;
                        circlePoints = circlePoints.concat(newPoints);
                    }
                    let circles = options.svg.selectAll('.traceBubble').data(circlePoints, (d: PlayChartDataPoint) => d.identity.getKey() + d.x + d.y + d.size);
                    circles.enter()
                        .append('circle')
                        .style('opacity', 0) //fade new bubbles into visibility
                        .classed('traceBubble', true);
                    circles
                        .attr('cx', (d: PlayChartDataPoint) => xScale(d.x))
                        .attr('cy', (d: PlayChartDataPoint) => yScale(d.y))
                        .attr('r', (d: PlayChartDataPoint) => PlayChart.getBubbleRadius(d.radius, options.data.sizeRange, options.data.currentViewport))
                        .style({
                            'stroke-opacity': (d: PlayChartDataPoint) => PlayChart.getBubbleOpacity(d, true),
                            'stroke-width': '1px',
                            'stroke': (d: PlayChartDataPoint) => PlayChart.getStrokeFill(d, true),
                            'fill': (d: PlayChartDataPoint) => d.fill,
                            'fill-opacity': (d: PlayChartDataPoint) => d.size != null ? PlayChart.getBubbleOpacity(d, true) : 0,
                        })
                        .transition()
                        .duration(PlayChart.FrameDuration)
                        .style('opacity',1);
                    circles.exit()
                        .transition()
                        .duration(PlayChart.FrameDuration)
                        .style('opacity', 0) //fade exiting bubbles out
                        .remove();

                    TooltipManager.addTooltip(circles, (tooltipEvent: TooltipEvent) => tooltipEvent.data.tooltipInfo);

                    // sort the z-order, smallest size on top
                    circles.sort((d1: PlayChartDataPoint, d2: PlayChartDataPoint) => { return d2.size - d1.size; });
                }
                else {
                    options.svg.selectAll('.traceLine').remove();
                    options.svg.selectAll('.traceBubble').remove();
                }
            }
            else {
                options.svg.selectAll('.traceLine').remove();
                options.svg.selectAll('.traceBubble').remove();
            }

            options.data.lastRenderedFrameIndex = options.data.currentFrameIndex;
        }

        private play(): void {
            if (this.isPlaying) {
                // Toggle the flag and allow the animation logic to kill it
                this.isPlaying = false;
            }
            else if ((<any>this.slider[0]).noUiSlider != null) {
                this.isPlaying = true;
                this.playButton.removeClass('play').addClass('pause');

                let indexToShow = Math.round((<any>this.slider[0]).noUiSlider.get());
                if (indexToShow >= this.data.allDataPoints.length - 1) {
                    this.data.currentFrameIndex = -1;
                } else {
                    this.data.currentFrameIndex = indexToShow - 1;
                }

                this.playNextFrame();
            }
        }

        private persistFrameIndex(frameIndex: number): void {
            this.ridiculousFlagForPersistProperties = true;

            let properties: { [name: string]: data.SQExpr } = {};
            properties['frozenIndex'] = data.SQExprBuilder.integer(frameIndex);

            this.host.persistProperties([{
                selector: null,
                properties: properties,
                objectName: 'general'
            }]);
        }

        private playNextFrame(startFrame?: number, endFrame?: number): void {
            if (!this.isPlaying) {
                this.playComplete();
                return;
            }

            let nextFrame = this.data.currentFrameIndex + 1;
            if (startFrame != null && endFrame != null) {
                nextFrame = Math.abs(endFrame - startFrame + 1);
                startFrame = nextFrame;
            }

            if (nextFrame < this.data.allDataPoints.length && nextFrame > -1) {
                this.data.currentFrameIndex = nextFrame;
                this.data.dataPoints = this.data.allDataPoints[nextFrame];
                this.persistFrameIndex(nextFrame); //this will cause a render call
                (<any>this.slider[0]).noUiSlider.set([nextFrame]);

                if (nextFrame < this.data.allDataPoints.length) {
                    let timePerFrame = PlayChart.FrameDuration;
                    window.setTimeout(() => {
                        // Update the rangeSlider to show the correct offset
                        (<any>this.slider[0]).noUiSlider.set([nextFrame]);
                        // Play next frame
                        this.playNextFrame(startFrame, endFrame);
                    }, timePerFrame);
                }
            } else {
                this.playComplete();
            }
        }

        private playComplete(): void {
            this.playButton.removeClass('pause').addClass('play');
            this.isPlaying = false;
            this.render(false);
        }

        private drawPlayMarkers(playData: PlayChartDataPoint[], hasSelection: boolean, sizeRange: NumberRange, suppressAnimations: boolean) {
            let duration = PlayChart.FrameDuration;

            let xScale = this.xAxisProperties.scale;
            let yScale = this.yAxisProperties.scale;

            let markerTagName = PlayChart.PlayChartCircleTagName;
            let markers = this.mainGraphicsContext.selectAll(PlayChart.DotClasses.selector).data(playData, (d: PlayChartDataPoint) => d.identity.getKey());

            markers.enter().append(markerTagName)
                .classed(PlayChart.DotClasses.class, true)
                .style('opacity', 0) //fade new bubbles into visibility
                .attr('r', 0);

            markers
                .style({
                    'stroke-opacity': (d: PlayChartDataPoint) => PlayChart.getBubbleOpacity(d, hasSelection),
                    'stroke-width': '1px',
                    'stroke': (d: PlayChartDataPoint) => PlayChart.getStrokeFill(d, true),
                    'fill': (d: PlayChartDataPoint) => d.fill,
                    'fill-opacity': (d: PlayChartDataPoint) => d.size != null ? PlayChart.getBubbleOpacity(d, hasSelection) : 0,
                });

            if (suppressAnimations) {
                markers
                    .style('opacity', 1)
                    .attr({
                        r: (d: PlayChartDataPoint) => PlayChart.getBubbleRadius(d.radius, sizeRange, this.currentViewport),
                        cx: d => xScale(d.x),
                        cy: d => yScale(d.y),
                    });
            }
            else {
                markers
                    .transition()
                    .duration(duration)
                    .style('opacity', 1)
                    .attr({
                        r: (d: PlayChartDataPoint) => PlayChart.getBubbleRadius(d.radius, sizeRange, this.currentViewport),
                        cx: d => xScale(d.x),
                        cy: d => yScale(d.y),
                    });
            }

            markers
                .exit()
                .transition()
                .duration(duration)
                .style('opacity', 0) //fade out bubbles that are removed
                .attr('r', 0)
                .remove();

            return markers;
        }

        public static getStrokeFill(d: ScatterChartDataPoint, colorBorder: boolean): string {
            if (d.size != null && colorBorder) {
                let colorRgb = Color.parseRgb(d.fill);
                return Color.rgbToHexString(Color.darken(colorRgb, ScatterChart.StrokeDarkenColorValue));
            }
            return d.fill;
        }

        public static getBubblePixelAreaSizeRange(viewPort: IViewport, minSizeRange: number, maxSizeRange: number): DataRange {
            let ratio = 1.0;
            if (viewPort.height > 0 && viewPort.width > 0) {
                let minSize = Math.min(viewPort.height, viewPort.width);
                ratio = (minSize * minSize) / PlayChart.AreaOf300By300Chart;
            }

            let minRange = Math.round(minSizeRange * ratio);
            let maxRange = Math.round(maxSizeRange * ratio);
            return {
                minRange: minRange,
                maxRange: maxRange,
                delta: maxRange - minRange
            };
        }

        public static project(value: number, actualSizeDataRange: DataRange, bubblePixelAreaSizeRange: DataRange): number {
            if (actualSizeDataRange.delta === 0 || bubblePixelAreaSizeRange.delta === 0) {
                return (PlayChart.rangeContains(actualSizeDataRange, value)) ? bubblePixelAreaSizeRange.minRange : null;
            }

            let relativeX = (value - actualSizeDataRange.minRange) / actualSizeDataRange.delta;
            return bubblePixelAreaSizeRange.minRange + relativeX * bubblePixelAreaSizeRange.delta;
        }

        public static projectSizeToPixels(size: number, actualSizeDataRange: DataRange, bubblePixelAreaSizeRange: DataRange): number {
            let projectedSize = 0;
            if (actualSizeDataRange) {
                // Project value on the required range of bubble area sizes
                projectedSize = bubblePixelAreaSizeRange.maxRange;
                if (actualSizeDataRange.delta !== 0) {
                    let value = Math.min(Math.max(size, actualSizeDataRange.minRange), actualSizeDataRange.maxRange);
                    projectedSize = PlayChart.project(value, actualSizeDataRange, bubblePixelAreaSizeRange);
                }

                projectedSize = Math.sqrt(projectedSize / Math.PI) * 2;
            }

            return Math.round(projectedSize);
        }

        public static rangeContains(range: DataRange, value: number): boolean {
            return range.minRange <= value && value <= range.maxRange;
        }

        public static getBubbleOpacity(d: PlayChartDataPoint, hasSelection: boolean): number {
            if (hasSelection && !d.selected) {
                return PlayChart.DimmedBubbleOpacity;
            }
            return PlayChart.DefaultBubbleOpacity;
        }

        public onClearSelection(): void {
            if (this.interactivityService)
                this.interactivityService.clearSelection();
        }

        public getSupportedCategoryAxisType(): string {
            return axisType.scalar;
        }
    }
}


text:
Ruth Merckle (* 28. April 1937 in Ulm; † 30. April 2018 ebenda) war eine deutsche Unternehmerin. Sie war außerdem vielfältig im sozialen und kirchlichen Bereich, hier insbesondere in der Evangelischen Kirche Deutschlands, aktiv und wurde für ihr langjähriges ehrenamtliches Engagement mehrfach geehrt.

Leben
Ruth Merckle wurde am 28. April 1937 in Ulm als Tochter von Erna und Kurt Holland geboren. Sie entstammte der Ulmer Zement-Dynastie Schwenk/Schleicher. Die gelernte Krankengymnastin und Mutter von vier Kindern hat gemeinsam mit ihrem Mann Adolf Merckle den weiteren Aufbau der Firmen Merckle und ratiopharm vorangetrieben, nachdem dieser die Firma Merckle 1967 übernommen hatte. Von 1974 bis 2002 war sie Geschäftsführerin. Sie war für die Bereiche Unternehmenskultur, Architektur und soziale Belange zuständig. Die Vereinbarkeit von Beruf und Familie und damit einhergehende familienfreundliche Teilzeit- und Heimarbeitsplätze waren ihr wichtig, ebenso wie die Betreuung von Kindern der Mitarbeiter in den Ferien und die Gründung eines Betriebskindergartens. Merckle führte die Position einer Frauenbeauftragten ein, die sich der Belange zunächst der Mitarbeiterinnen, später im "Treffpunkt Mensch" der Belange aller Mitarbeiter angenommen hat. Lehrlinge konnten bei einem Klosteraufenthalt im thüringischen Kloster Volkenroda oder beim Christlichen Jugenddorfwerk Deutschlands (CJD) eine Woche Teamerfahrung sammeln und in einer "Juniorfirma" Selbstständigkeit und Betriebsabläufe erlernen. Merckle setzte auch eine Kunstbeauftragte ein, und so konnten Mitarbeiter ihre Kunst am Arbeitsplatz auswählen.
Bergsteigen war ihr Hobby. Das Ehepaar Merckle unternahm Trekkingtouren im Himalaya, geführt von Everest-Besteiger Siegfried Hupfauer. Zusammen mit Hupfauer war Ruth Merckle 1983 die offizielle Erstbesteigerin eines Berges in Bhutan. Der 5.250 Meter hohe Berg wurde anschließend nach Merckle Ruth benannt: Meru Kang II.

Ehrenamt
Merckle war 31 Jahre Kirchengemeinderätin in Blaubeuren, hat sich in der lokalen Hospiz-Gruppe eingebracht, war Mitglied des Stiftungsrates in der Stiftung Kloster Volkenroda (Thüringen) und der Hoffnungsträger Stiftung, im Präsidium des Christlichen Jugenddorfwerk Deutschlands und im Kuratorium des Vereins Seehaus Leonberg. Von 1991 bis 1998 war sie im Rat der EKD, dem höchsten Gremium der Evangelischen Kirche Deutschlands und von 1992 bis 2005 im Vorstand des Arbeitskreises Evangelischer Unternehmer. Dabei diente sie als "Übersetzerin zwischen Kirche und Wirtschaft" und hat zum gegenseitigen Verständnis beigetragen. 1997 wirkte sie bei dem Sozialwort der Kirchen "Für eine Zukunft in Solidarität und Gerechtigkeit" mit.

Ehrungen und Auszeichnungen
 1990 erhielt Merckle die Wirtschaftsmedaille des Landes Baden-Württemberg.
 1994 wurde Merckle zur Ehrenbürgerin der Stadt Blaubeuren ernannt.
 2003 wurde ihr der Verdienstorden des Landes Baden-Württemberg verliehen.
 2007 hat sie das Goldene Ehrenzeichen des Christlichen Jugenddorfwerk Deutschlands bekommen.

Einzelnachweise

Unternehmer (Baden-Württemberg)
Unternehmer (20. Jahrhundert)
Person (Evangelische Kirche in Deutschland)
Träger des Verdienstordens des Landes Baden-Württemberg
Ehrenbürger im Alb-Donau-Kreis
Person (Ulm)
Deutscher
Geboren 1937
Gestorben 2018
Frau
Merckle
Träger der Wirtschaftsmedaille des Landes Baden-Württemberg

redpajama_set_name: RedPajamaWikipedia


Eintracht Frankfurt have announced the signing of midfielder Jonathan de Guzman from Napoli on a three-year contract.
A statement on Frankfurt's official website confirmed the news but stressed his arrival remained subject to "all the documents being signed".
A transfer fee is also undisclosed, although the Dutchman had only one year left on his Napoli contract.
The 29-year-old was bought by Rafa Benitez in 2014 and scored seven goals in 36 appearances for the Partenopei in his first season.
However, he was deemed surplus to requirements by Maurizio Sarri and spent the past 18 months on loan at Carpi and Chievo.


text:
describe('Observe', function(){
    it('Should have Observer', function(){
        chai.expect(Anno.classes.Observer).to.not.be.undefined;
    });
    it('Can see changes', function() {
        var Child = Anno.classes.Observer.extend({
            constructor: function() {
                Child._super.constructor.call(this);
            }
        });
        var o = new Anno.classes.Observer({
            list: Child,
            child: Child
        });
        o.silentFill({
            name: "Observer",
            property: true,
            list: [],
            plainList: [],
            child: {}
        });
        var arrayDelegate = {
            arrayPush: function(ignore, method, param) {
                if (param == "list") chai.expect(arguments[3]).to.be.instanceof(Child);
                if (param == "plainList") chai.expect(arguments[3]).to.be.instanceof(Anno.classes.Observer);
            },
            arrayPop: function() {
                chai.expect(o.list[o.list.length-1]).to.have.deep.property("name", "Child 5");
            },
            arrayShift: function() {
                chai.expect(o.list[0]).to.have.deep.property("name", "Child 2");
            },
            arrayUnshift: function(ignore, method, param) {
                chai.expect(o.list[0]).to.have.deep.property("name", "Child 1");
            },
            arraySet: function(ignore, method, param, idx, obj) {
                chai.expect(o.list[idx]).to.have.deep.property("name", "Child A");
            },
            arraySplice: function(ignore, method, param, arr, idx, count) {
                chai.expect(o.list[idx]).to.have.deep.property("name", "Child 0");
            },
            arrayReverse: function() {
                chai.expect(o.list[0]).to.have.deep.property("name", "Child 5");
            },
            arraySort: function(ignore, method, param, indexArr) {
                chai.expect(o.list[0]).to.have.deep.property("name", "Child 0");
                chai.expect(o.list[o.list.length-1]).to.have.deep.property("name", "Child 5");
            },
            arrayLength: function(ignore, method, param, len, oldLen) {
                chai.assert.notEqual(len, oldLen);
            }
        }
        var propDelegate = {
            property: function(type, method, property, val, oldval) {
                if (property == "name") {
                    chai.assert.equal(type, "property");
                    chai.assert.equal(method,"set");
                    chai.assert.equal(property,"name");
                    chai.assert.equal(val,"New name");
                    chai.assert.equal(oldval,"Observer");
                }
                if (property == "child") {
                    chai.expect(val).to.be.instanceof(Child);
                }
            }
        }
        o.observe("list", function(type, method, property) {
            arrayDelegate["array"+method.capitalize(true)].apply(this, arguments);
        });
        o.observe("plainList", function(type, method, property) {
            arrayDelegate["array"+method.capitalize(true)].apply(this, arguments);
        });
        o.observe("name", function(type, method, property) {
            propDelegate["property"].apply(this, arguments);
        });
        o.observe("child", function(type, method, property) {
            propDelegate["property"].apply(this, arguments);
        });
        o.name = "New name";
        o.list.push({name: "Child 1"},{name: "Child 2"},{name: "Child 3"},{name: "Child 4"},{name: "Child 5"},{name: "Child 6"});
        o.list.pop();
        o.list.shift();
        o.list.unshift({name: "Child 1"});
        o.list.set(1, {name: "Child A"});
        o.list.splice(1, 1, {name: "Child 0"});
        o.list.reverse();
        o.list.sort(function(a,b) {
            if (a.name > b.name)
                return 1;
            if (a.name < b.name)
                return -1;
            // a must be equal to b
            return 0;
        });
        o.plainList.push({name: "Test"});
        o.child = {name: "child"};
    })
    it('Aop and observer is working', function() {
        var o = new Anno.classes.Observer(),
            call = 1;
        o.silentFill({
            arr: Anno.asSet([])
        })
        o.observe("arr", function(type, method, property, a1, a2) {
            if (method == "push") {
                if (call == 1) chai.expect(a2).to.be.undefined;
                if (call == 2) {
                    chai.expect(a1).to.be.undefined;
                    chai.expect(a2).to.be.undefined;
                }
                call++;
            }
        });
        o.arr.push("a","a");
        o.arr.push("a","a");
    });

    it('Can fill observer with .fill()', function() {
        var observed = 0;
        var o = new Anno.classes.Observer({
            arr: Anno.classes.ChildModel
        });
        o.silentFill({
            prop: "text",
            arr: []
        })
        o.observe("arr", function(type, method, property, a1, a2) {
            if (method == "push") {
                observed++;
            }
        });
        o.observe("prop", function(type, method, property, a1, a2) {
            observed++;
        });
        o.arr.push({});
        chai.expect(o.arr[0].name).to.be.equal("Son");
        o.fill({
            prop: "new",
            arr: [{name: "Daughter1"},{name: "Daughter2"}]
        })
        chai.expect(o.prop).to.be.equal("new");
        chai.expect(o.arr.length).to.be.equal(2);
        chai.expect(o.arr[1].name).to.be.equal("Daughter2");
        chai.expect(o.arr[1]).to.be.an.instanceOf(Anno.classes.ChildModel);
        chai.expect(observed).to.be.equal(2);
    });
});

redpajama_set_name: RedPajamaGithub


The biogas sector grows impressively every year across the European Union. The sector's progress is evident, since European primary energy growth leapt by a further 4.3% in 2009, reaching an energy output of 8.3 Mtoe. In 2011, 10 Mtoe have been produced in the European Union. Germany has a leading role in Europe with almost 7 400 biogas plants in 2012, but the German market is no longer Europe's sole market driver. Italy's growth forecasts are particularly high, and they should be matched by those of France, Spain and the UK. New markets are emerging in Eastern Europe such as the Czech Republic, Slovakia and Hungary. The potential for biogas production in 2020 is estimated at 40 Mtoe according to AEBIOM and the BiogasIN project, mostly concentrated in France, Germany and Britain.
Biogas energy is mainly recovered in the form of electricity. In 2009, 25.2 TWh were produced from biogas, which is an increase of 17.9% on 2008. Actual figures are difficult to obtain for the whole Europe, but only in Germany, the 2012 production was above 22 TWh electricity.
Increasingly cogeneration plants produce this electricity and, at the same time, also supply heat (combined heat and power production, CHP). Biogas produced by the anaerobic digestion of biomass is also a valuable renewable energy source that can be exploited directly as a raw material for the production of synthesis gas and/or hydrogen for industrial syntheses or energetic purposes.
Under an optimistic scenario, the 2 Billion EUR biogas plant installations business could grow up to 25 Billion by 2020.
However, as also pointed out in the roadmap of the BiogasIN project (N. Bachmann, A. Wellinger, D6.3 Pan-European Biogas Roadmap) today the diversity of regulations and variety of EU practices and technological choices for safety and environment at design and operation stages represent major barriers for the increased production and uses of biogas. It creates delays for an operator who wants to exploit resources to produce biogas, it generate unnecessary costs when a European operator has to adapt its technologies to the national non-harmonized safety and environmental requirements, and generate confusion among the public that might hamper the acceptance of biogas as a key renewable energy source for Europe by 2020.
These statements are the main motivations for the creation of the European Working Group on Biogas Safety & Regulation (EWGBSR).
Share information, data, good practices and experiences on biogas safety and regulation, including accidents and near-misses.
Collect the experience feedbacks on accidents, incidents, failures, breakdowns.
Agree on gaps in terms of safety and regulation.
Propose solutions to fill those gaps by initiating projects (possible external European funding such as EU Framework Programme, Competitiveness and Innovation Programme, EUREKA, SET-Plan Technology Roadmap…).
Production of Biogas - Safety & Regulations, November 24th, 2010 in Paris.
This workshop has been organised by INERIS and EU-VRi. It has been the initiating event of the European Working Group on Biogas Safety & Regulation.


The second you land on Katie Momo's freelance copywriting website, you know she's one of a kind. Her fresh, fun outlook leaps off the page.
Her specialty is product launches, and her success with her freelance client launches landed her a sweet in-house copywriting job.
Tune in to hear about her journey that led to working with a hugely successful start-up company and the unique projects she's working on now.
Our chat will leave you motivated and encouraged. Don't miss it!


Wow, I had such a great time with you tonight. I was a little worried about doing this whole blind date thing but it really turned out great. The venue was amazing, the food was delicious, and our conversation was only moderately forced and uncomfortable.
God, and that was such a romantic Uber ride home. I loved the way you drunkenly told the driver about your fourth-grade birthday party even though he didn't ask you about it. So hot. Now that we're back at my place in the high rises, the real fun can begin.
Oh, that sound? Yeah, that's just my roommate Grayson. Don't worry about him though, baby, it's just you and me tonight. Your place? Nah girl, we got everything we need right here. You, me, a thin strip of wall between my bed and Grayson's — let's get wild. Grayson actually has a pretty early morning though tomorrow, so let's just be real quiet.
There's nothing to be uncomfortable about. These high-rise rooms were built for this exact type of situation. Based on my past sexual experiences, you probably won't be doing much yelling regardless, so this is no different. If you really don't want to, though, I understand. Let's just go to the rooftop lounge and plow with a view.


text:
Sex Ed Debate Brings Anti-LGBTQ Voices to the Arizona Capitol
Arizona Sues E-Cigarette Giants JUUL Labs, Eonsmoke Amid 'Public Health Crisis'
Tapes Released of 9-1-1 Call from Mother of 3-Year-Old Boy Found Dead in Car
James King | July 14, 2010 | 2:04pm
There's speculation that the 3-year-old boy who was found dead in his family's car yesterday was left in the car by his mother, which goes against her claim that she took her eye off of him for a few minutes and he wandered into the car on his own.
Today, the Chandler Police Department released the 9-1-1 call the mother placed when she couldn't find her son, and she sounds more like a frantic woman looking for her child than someone who just found her son dead in her car and is trying to cover her tracks.
Check out the audio after the jump.
In the tapes, you can hear the boy's mother explain to dispatchers that she was studying for about 40 minutes, and when she went to look for her son, he was gone.
of the 9-1-1 call.
Carpenter claims she searched the house and checked with neighbors but couldn't find the boy and called police.
Police responded and found the boy unresponsive in the front seat of the family car in the driveway of the house.
The boy was taken to the hospital where he was later pronounced dead.
One of Carpenter's neighbors, Joe Therrien, told KPHO yesterday that he has his own theory as to what happened.
"How can a 3-year old open a door, for one thing, then shut the door?" asked Therrien. "I'm thinking she probably left him in the car."
Despite Therrien's assessment of the situation, we heard this morning that the incident had already been ruled an accident and the mother was off the hook. That information turned out to be a rumor, though, which was put to an end by Chandler police Sergeant Joe Favazzo who seemed shocked we even asked.
"What? Where did you hear that," Favazzo asked. "We're still actively investigating this case."
Favazzo says the findings of the investigation will go to the Maricopa County Attorney's Office, which will make the decision on whether to charge Carpenter with any crime.
Maricopa County Assessor Paul Petersen Resigns Over Adoption...
Chandler Hit-and-Run Highlights Tech Companies' Cozy...
TAMPON, DENIED: Arizona's Rejected Vanity Plates
Bill Seeks $6.1 Million and More Hydrologists for Arizona...

redpajama_set_name: RedPajamaCommonCrawl


Comment: a haiku that evokes both a sense of melancholy (wabi 侘) and inexplicable depth (yūgen 幽玄) in just 8 syllables and 7 words. A work that puts an accent on the unsaid, creating an 'empty dimension' (yohaku 余白) where the scene is portrayed by the surrounding silence rather then by words.
The wind (kaze 風) in the first ku renders the lightness (karumi 軽み) and impermanence (mujō 無常) of the scene in a sensory way, bringing a message of hope (the 'prayer' in line 2) that is destined to vanish.


Where do you get your dried pigeon peas?
I have to order them. Often I get them (whole toor dal) from https://shop.khanapakana.com/swad-toor-whole-pigeon-peas-2-lb-907-grams/ I need to get on top of things and put in an order, actually.


text:
What science can do
LabTalk blog
Drug modalities
Our therapy areas
All therapy areas
Cardiovascular, Renal and Metabolism
Global careers site
Investor Relations (Global)
Investor Relations (Sweden)
ADR Programme
Partnering with AstraZeneca
Our Partnering teams
Our areas of partnering interest
Why partner with AstraZeneca?
Secrets to successful partnering
Broadcast videos
AstraZeneca Websites
Tagrisso approved in China as first-in-class treatment for EGFR T790M mutation-positive metastatic non-small cell lung cancer
First AstraZeneca medicine approved under China's new Priority Review pathway
Tagrisso is an important new medicine in a country where lung cancer is the leading cause of cancer-related deaths and EGFR-mutation rates are among highest globally
AstraZeneca today announced that the China Food and Drug Administration (CFDA) has granted marketing authorisation for Tagrisso (osimertinib) 40 mg and 80mg once-daily oral tablets for the treatment of adult patients with locally-advanced or metastatic epidermal growth factor receptor (EGFR) T790M mutation-positive non-small cell lung cancer (NSCLC) whose disease has progressed on or after EGFR tyrosine kinase inhibitor (TKI) therapy.
Lung cancer is the most common form of cancer and the leading cause of cancer-related deaths in China.1 Approximately 30-40% of Asian patients with NSCLC have the EGFR mutation at diagnosis, and nearly two thirds of these patients whose disease progresses after treatment with an EGFR TKI develop the T790M mutation.2,3
Sean Bohen, Executive Vice President, Global Medicines Development and Chief Medical Officer at AstraZeneca, said: "This is an important step forward for Tagrisso and a significant opportunity to bring a breakthrough medicine to patients with NSCLC in China, where EGFR mutation rates are some of the highest in the world."
Eligibility for treatment with Tagrisso requires the patient's EGFR T790M mutation status to be determined with a validated test using either tumour DNA derived from a tissue sample or circulating tumour DNA (ctDNA) obtained from a plasma sample (blood test).
Tagrisso is the first AstraZeneca medicine approved under the CFDA's Priority Review pathway, using an accelerated timeline for an innovative medicine. The rapid review and approval signal the urgent need for new, targeted treatments with the potential to address specific types of cancer with high incidence rate and significant unmet medical need in China. They are also a recognition by the CFDA of the accelerated reform of China's regulatory framework to benefit Chinese patients.
About AstraZeneca in China
Since entering China in 1993, AstraZeneca has been committed to continuously following the science, focusing on innovation and becoming one of the most trusted healthcare partners in improving the lives of Chinese patients. AstraZeneca's China headquarters are based in Shanghai, and the company has more than 11,000 employees throughout the country. AstraZeneca has end-to-end R&D capabilities in China, from discovery through to clinical development and manufacturing of innovative medicines to address the unique needs of patients in Asia. This comprehensive presence includes manufacturing sites in Wuxi and Taizhou, a China Distribution Centre in Wuxi and an Innovation Centre in Shanghai. China is AstraZeneca's second largest market globally, with revenue of $2.6 billion in 2016.
About Non-Small Cell Lung Cancer (NSCLC)
Lung cancer is the leading cause of cancer death among both men and women, accounting for about one-third of all cancer deaths and more than breast, prostate and colorectal cancers combined.4 Among patients with NSCLC, 20% to 40% have brain metastases at some time during the course of their disease.5 Patients who have the EGFRm form of NSCLC, which occurs in 10-15% of NSCLC patients in the US and Europe and 30-40% of NSCLC patients in Asia, are particularly sensitive to treatment with currently-available EGFR-TKIs, which block the cell signalling pathways that drive the growth of tumour cells.2,6,7,8 However, tumours almost always develop resistance to treatment, leading to disease progression.9 Approximately two-thirds of patients develop resistance to approved EGFR-TKIs such as gefitinib and erlotinib due to the secondary mutation, T790M.9
About Tagrisso
Tagrisso (osimertinib) 40 mg and 80mg once daily oral tablet has been approved in over 45 countries, including the US, EU, Japan and China, for patients with EGFR T790M mutation-positive advanced non-small cell lung cancer (NSCLC). Eligibility for treatment with Tagrisso is dependent on confirmation that the EGFR T790M mutation is present in the tumour.
Tagrisso is a third generation, irreversible EGFR inhibitor designed to inhibit both EGFR sensitising and T790M resistance mutations and to have activity in the central nervous system (CNS). Tagrisso is also investigated in the adjuvant and metastatic 1st line settings, including in patients with and without brain metastases, in leptomeningeal disease, and in combination with other treatments.
About AstraZeneca in Oncology
AstraZeneca has a deep-rooted heritage in Oncology and offers a quickly growing portfolio of new medicines that has the potential to transform patients' lives and the Company's future. With at least six new medicines to be launched between 2014 and 2020 and a broad pipeline of small molecules and biologics in development, we are committed to advance New Oncology as one of AstraZeneca's six Growth Platforms focused on lung, ovarian, breast and blood cancers. In addition to our core capabilities, we actively pursue innovative partnerships and investments that accelerate the delivery of our strategy, as illustrated by our investment in Acerta Pharma in hematology.
By harnessing the power of four scientific platforms – Immuno-Oncology, the genetic drivers of cancer and resistance, DNA Damage Response and Antibody Drug Conjugates – and by championing the development of personalized combinations, AstraZeneca has the vision to redefine cancer treatment and one day eliminate cancer as a cause of death.
About AstraZeneca
AstraZeneca is a global, science-led biopharmaceutical company that focuses on the discovery, development and commercialisation of prescription medicines, primarily for the treatment of diseases in three main therapy areas - Oncology, Cardiovascular & Metabolic Diseases and Respiratory. The Company also is selectively active in the areas of autoimmunity, neuroscience and infection. AstraZeneca operates in over 100 countries and its innovative medicines are used by millions of patients worldwide. For more information, please visit www.astrazeneca.com and follow us on Twitter @AstraZeneca.
Esra Erkal-Paler
Vanessa Rhodes
Karen Birmingham
Rob Skelding
Michele Meixell
Thomas Kudsk Larsen
Craig Marks
Finance, Fixed Income, M&A
Henry Wheeler
Mitchell Chan
Lindsey Trickett
Cardiovascular & Metabolic Diseases
Christer Gruvris
Autoimmunity, neuroscience & infection
1 Zhou C. Lung Cancer Molecular Epidemiology in China: Recent Trends. Transl Lung Cancer Res. 2014;3(5):270–279.
2 Ellison G, et al. EGFR Mutation Testing in Lung Cancer: A Review of Available Methods and Their Use for Analysis of Tumour Tissue and Cytology Samples. J Clin Pathol. 2013;66:79-89.
3 Sharma SV, et al. Epidermal Growth Factor Receptor Mutations in Lung Cancer. Nat Rev Cancer. 2007;7:169-181.
4 GLOBOCAN (2012). Estimated cancer incidence, mortality and prevalence worldwide in 2012. Available at: http://globocan.iarc.fr/Pages/fact_sheets_cancer.aspx. Accessed March 2017.
5 Ali A, et al. Survival of Patients with Non-Small-Cell Lung Cancer After a Diagnosis of Brain Metastases. Curr Oncol. 2013;20(4):e300–e306.
6 Szumera-Ciećkiewicz A, et al. EGFR Mutation Testing on Cytological and Histological Samples in Non-Small Cell Lung Cancer: a Polish, Single Institution Study and Systematic Review of European Incidence. Int J Clin Exp Pathol. 2013;6:2800-12.
7 Keedy VL, et al. American Society of Clinical Oncology Provisional Clinical Opinion: Epidermal Growth Factor Receptor (EGFR) Mutation Testing for Patients with Advanced Non-Small-Cell Lung Cancer Considering First-Line EGFR Tyrosine Kinase Inhibitor Therapy. J Clin Oncol. 2011;29:2121-27.
8 Langer CJ, et al. Epidermal Growth Factor Receptor Inhibition in Mutation-Positive Non-Small-Cell Lung Cancer: Is Afatinib Better or Simply Newer? Journal of Clinical Oncology. 2013;31(27);3303-3305.
9 Yu HA, et al. Analysis of Tumour Specimens at the Time of Acquired Resistance to EGFR-TKI Therapy in 155 Patients with EGFR-Mutant Lung Cancer. Clin Cancer Research. 2013:19(8):2240-2246.
Corporate and financial
This website is intended for people seeking information on AstraZeneca's worldwide business. Our country sites can be located in the AZ Network.
Veeva ID: Z4-8411
Date of next review: August 2020
Externally sponsored scientific research
US prescription assistance programmes
Global clinical trials information
Report a possible side effect
Pearl Therapeutics
You are now leaving AstraZeneca.com
You have selected a link that will take you to a site maintained by a third party who is solely responsible for its contents.
AstraZeneca provides this link as a service to website visitors. AstraZeneca is not responsible for the privacy policy of any third party websites. We encourage you to read the privacy policy of every website you visit.
Click 'cancel' to return to AstraZeneca's site or 'continue' to proceed.