From 9167f7e1149e81e5835a647da6f4cc5b69c718b2 Mon Sep 17 00:00:00 2001 From: Simon Pedersen Date: Mon, 22 Aug 2016 22:35:28 +0200 Subject: [PATCH] initial --- .api/v1.1.0/PowerBI-visuals.d.ts | 1977 ++++++++++++++++++++++++++ .api/v1.1.0/schema.capabilities.json | 721 ++++++++++ .api/v1.1.0/schema.pbiviz.json | 95 ++ .gitignore | 2 + .npmignore | 5 + .vscode/launch.json | 13 + .vscode/settings.json | 31 + assets/icon.png | Bin 0 -> 1162 bytes capabilities.json | 106 ++ package.json | 3 + pbiviz.json | 23 + src/visual.ts | 477 +++++++ style/visual.less | 9 + tsconfig.json | 14 + 14 files changed, 3476 insertions(+) create mode 100644 .api/v1.1.0/PowerBI-visuals.d.ts create mode 100644 .api/v1.1.0/schema.capabilities.json create mode 100644 .api/v1.1.0/schema.pbiviz.json create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 assets/icon.png create mode 100644 capabilities.json create mode 100644 package.json create mode 100644 pbiviz.json create mode 100644 src/visual.ts create mode 100644 style/visual.less create mode 100644 tsconfig.json diff --git a/.api/v1.1.0/PowerBI-visuals.d.ts b/.api/v1.1.0/PowerBI-visuals.d.ts new file mode 100644 index 0000000..8cecb9b --- /dev/null +++ b/.api/v1.1.0/PowerBI-visuals.d.ts @@ -0,0 +1,1977 @@ +/* + * Power BI Visual CLI - Visual API v1.1.0 + * + * Copyright (c) Microsoft Corporation + * All rights reserved. + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the ""Software""), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +declare module powerbi { + enum VisualDataRoleKind { + /** Indicates that the role should be bound to something that evaluates to a grouping of values. */ + Grouping = 0, + /** Indicates that the role should be bound to something that evaluates to a single value in a scope. */ + Measure = 1, + /** Indicates that the role can be bound to either Grouping or Measure. */ + GroupingOrMeasure = 2, + } + enum VisualDataChangeOperationKind { + Create = 0, + Append = 1, + } + enum VisualUpdateType { + Data = 2, + Resize = 4, + ViewMode = 8, + Style = 16, + ResizeEnd = 32, + All = 62, + } + enum VisualPermissions { + } + const enum CartesianRoleKind { + X = 0, + Y = 1, + } + const enum ViewMode { + View = 0, + Edit = 1, + } + const enum ResizeMode { + Resizing = 1, + Resized = 2, + } + module visuals.telemetry { + const enum TelemetryCategory { + Verbose = 0, + CustomerAction = 1, + CriticalError = 2, + Trace = 3, + } + enum ErrorSource { + PowerBI = 0, + External = 1, + User = 2, + } + } + const enum JoinPredicateBehavior { + /** Prevent items in this role from acting as join predicates. */ + None = 0, + } + const enum PromiseResultType { + Success = 0, + Failure = 1, + } +} + + +declare module powerbi { + export interface DragPayload { + } +} + + + +declare module jsCommon { + export interface IStringResourceProvider { + get(id: string): string; + getOptional(id: string): string; + } +} + +declare module powerbi { + /** + * An interface to promise/deferred, + * which abstracts away the underlying mechanism (e.g., Angular, jQuery, etc.). + */ + export interface IPromiseFactory { + /** + * Creates a Deferred object which represents a task which will finish in the future. + */ + defer(): IDeferred; + + /** + * Creates a Deferred object which represents a task which will finish in the future. + */ + defer(): IDeferred2; + + /** + * Creates a promise that is resolved as rejected with the specified reason. + * This api should be used to forward rejection in a chain of promises. + * If you are dealing with the last promise in a promise chain, you don't need to worry about it. + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, + * think of reject as the throw keyword in JavaScript. + * This also means that if you "catch" an error via a promise error callback and you want + * to forward the error to the promise derived from the current promise, + * you have to "rethrow" the error by returning a rejection constructed via reject. + * + * @param reason Constant, message, exception or an object representing the rejection reason. + */ + reject(reason?: TError): IPromise2; + + /** + * Creates a promise that is resolved with the specified value. + * This api should be used to forward rejection in a chain of promises. + * If you are dealing with the last promise in a promise chain, you don't need to worry about it. + * + * @param value Object representing the promise result. + */ + resolve(value?: TSuccess): IPromise2; + + /** + * Combines multiple promises into a single promise that is resolved when all of the input promises are resolved. + * Rejects immediately if any of the promises fail + */ + all(promises: IPromise2[]): IPromise; + + /** + * Combines multiple promises into a single promise that is resolved when all of the input promises are resolved. + * Does not resolve until all promises finish (success or failure). + */ + allSettled(promises: IPromise2[]): IPromise[]>; + + /** + * Wraps an object that might be a value or a then-able promise into a promise. + * This is useful when you are dealing with an object that might or might not be a promise + */ + when(value: T | IPromise): IPromise; + } + + /** + * Represents an operation, to be completed (resolve/rejected) in the future. + */ + export interface IPromise extends IPromise2 { + } + + /** + * Represents an operation, to be completed (resolve/rejected) in the future. + * Success and failure types can be set independently. + */ + export interface IPromise2 { + /** + * Regardless of when the promise was or will be resolved or rejected, + * then calls one of the success or error callbacks asynchronously as soon as the result is available. + * The callbacks are called with a single argument: the result or rejection reason. + * Additionally, the notify callback may be called zero or more times to provide a progress indication, + * before the promise is resolved or rejected. + * This method returns a new promise which is resolved or rejected via + * the return value of the successCallback, errorCallback. + */ + then(successCallback: (promiseValue: TSuccess) => IPromise2, errorCallback?: (reason: TError) => TErrorResult): IPromise2; + + /** + * Regardless of when the promise was or will be resolved or rejected, + * then calls one of the success or error callbacks asynchronously as soon as the result is available. + * The callbacks are called with a single argument: the result or rejection reason. + * Additionally, the notify callback may be called zero or more times to provide a progress indication, + * before the promise is resolved or rejected. + * This method returns a new promise which is resolved or rejected via + * the return value of the successCallback, errorCallback. + */ + then(successCallback: (promiseValue: TSuccess) => TSuccessResult, errorCallback?: (reason: TError) => TErrorResult): IPromise2; + + /** + * Shorthand for promise.then(null, errorCallback). + */ + catch(onRejected: (reason: any) => IPromise2): IPromise2; + + /** + * Shorthand for promise.then(null, errorCallback). + */ + catch(onRejected: (reason: any) => TErrorResult): IPromise2; + + /** + * Allows you to observe either the fulfillment or rejection of a promise, + * but to do so without modifying the final value. + * This is useful to release resources or do some clean-up that needs to be done + * whether the promise was rejected or resolved. + * See the full specification for more information. + * Because finally is a reserved word in JavaScript and reserved keywords + * are not supported as property names by ES3, you'll need to invoke + * the method like promise['finally'](callback) to make your code IE8 and Android 2.x compatible. + */ + finally(finallyCallback: () => any): IPromise2; + } + + export interface IDeferred extends IDeferred2 { + } + + export interface IDeferred2 { + resolve(value: TSuccess): void; + reject(reason?: TError): void; + promise: IPromise2; + } + + export interface RejectablePromise2 extends IPromise2 { + reject(reason?: E): void; + resolved(): boolean; + rejected(): boolean; + pending(): boolean; + } + + export interface RejectablePromise extends RejectablePromise2 { + } + + export interface IResultCallback { + (result: T, done: boolean): void; + } + + export interface IPromiseResult { + type: PromiseResultType; + value: T; + } +} + +declare module powerbi.visuals { + export interface IRect { + left: number; + top: number; + width: number; + height: number; + } +} + +declare module powerbi.visuals { + import Selector = data.Selector; + + export interface ISelectionIdBuilder { + withCategory(categoryColumn: DataViewCategoryColumn, index: number): this; + withSeries(seriesColumn: DataViewValueColumns, valueColumn: DataViewValueColumn | DataViewValueColumnGroup): this; + withMeasure(measureId: string): this; + createSelectionId(): ISelectionId; + } + + export interface ISelectionId { + equals(other: ISelectionId): boolean; + includes(other: ISelectionId, ignoreHighlight?: boolean): boolean; + getKey(): string; + getSelector(): Selector; + getSelectorsByColumn(): Selector; + hasIdentity(): boolean; + } +} + +declare module powerbi.visuals { + export interface IPoint { + x: number; + y: number; + } +} + +declare module powerbi.data { + export interface CompiledDataViewMapping { + metadata: CompiledDataViewMappingMetadata; + categorical?: CompiledDataViewCategoricalMapping; + table?: CompiledDataViewTableMapping; + single?: CompiledDataViewSingleMapping; + tree?: CompiledDataViewTreeMapping; + matrix?: CompiledDataViewMatrixMapping; + scriptResult?: CompiledDataViewScriptResultMapping; + usage?: DataViewMappingUsage; + } + + export interface CompiledDataViewMappingScriptDefinition { + source: DataViewObjectPropertyIdentifier; + provider: DataViewObjectPropertyIdentifier; + imageFormat?: string; + scriptInput?: ScriptInput; + } + + export interface CompiledDataViewScriptResultMapping { + dataInput: CompiledDataViewMapping; + script: CompiledDataViewMappingScriptDefinition; + } + + export interface CompiledDataViewMappingMetadata { + /** The metadata repetition objects. */ + objects?: DataViewObjects; + } + + export interface CompiledDataViewCategoricalMapping extends HasDataVolume, HasReductionAlgorithm { + categories?: CompiledDataViewRoleMappingWithReduction | CompiledDataViewListRoleMappingWithReduction; + values?: CompiledDataViewRoleMapping | CompiledDataViewGroupedRoleMapping | CompiledDataViewListRoleMapping; + includeEmptyGroups?: boolean; + } + + export interface CompiledDataViewGroupingRoleMapping { + role: CompiledDataViewRole; + } + + export interface CompiledDataViewSingleMapping { + role: CompiledDataViewRole; + } + + export interface CompiledDataViewTableMapping extends HasDataVolume { + rows: CompiledDataViewRoleMappingWithReduction | CompiledDataViewListRoleMappingWithReduction; + } + + export interface CompiledDataViewTreeMapping extends HasDataVolume { + nodes?: CompiledDataViewRoleForMappingWithReduction; + values?: CompiledDataViewRoleForMapping; + } + + export interface CompiledDataViewMatrixMapping extends HasDataVolume { + rows?: CompiledDataViewRoleForMappingWithReduction | CompiledDataViewListRoleMappingWithReduction; + columns?: CompiledDataViewRoleForMappingWithReduction; + values?: CompiledDataViewRoleForMapping | CompiledDataViewListRoleMapping; + } + + export type CompiledDataViewRoleMapping = CompiledDataViewRoleBindMapping | CompiledDataViewRoleForMapping; + + export interface CompiledDataViewRoleBindMapping { + bind: { + to: CompiledDataViewRole; + }; + } + + export interface CompiledDataViewRoleForMapping { + for: { + in: CompiledDataViewRole; + }; + } + + export type CompiledDataViewRoleMappingWithReduction = CompiledDataViewRoleBindMappingWithReduction | CompiledDataViewRoleForMappingWithReduction; + + export interface CompiledDataViewRoleBindMappingWithReduction extends CompiledDataViewRoleBindMapping, HasReductionAlgorithm { + } + + export interface CompiledDataViewRoleForMappingWithReduction extends CompiledDataViewRoleForMapping, HasReductionAlgorithm { + } + + export interface CompiledDataViewGroupedRoleMapping { + group: CompiledDataViewGroupedRoleGroupItemMapping; + } + + export interface CompiledDataViewGroupedRoleGroupItemMapping extends HasReductionAlgorithm { + by: CompiledDataViewRole; + select: CompiledDataViewRoleMapping[]; + } + + export interface CompiledDataViewListRoleMapping { + select: CompiledDataViewRoleMapping[]; + } + + export interface CompiledDataViewListRoleMappingWithReduction extends CompiledDataViewListRoleMapping, HasReductionAlgorithm { + } + + export const enum CompiledSubtotalType { + None = 0, + Before = 1, + After = 2 + } + + export interface CompiledDataViewRole { + role: string; + items: CompiledDataViewRoleItem[]; + subtotalType?: CompiledSubtotalType; + showAll?: boolean; + activeItems?: string[]; + aggregates?: DataViewMappingRoleProjectionAggregates; + } + + export interface CompiledDataViewRoleItem { + queryName: string; + //changed to descriptor to not need to depend on ValueType class + type?: ValueTypeDescriptor; + joinPredicate?: JoinPredicateBehavior; + } +} + +declare module powerbi { + export const enum SortDirection { + Ascending = 1, + Descending = 2, + } +} + +declare module powerbi { + /** Represents views of a data set. */ + export interface DataView { + metadata: DataViewMetadata; + categorical?: DataViewCategorical; + single?: DataViewSingle; + tree?: DataViewTree; + table?: DataViewTable; + matrix?: DataViewMatrix; + scriptResult?: DataViewScriptResultData; + } + + export interface DataViewMetadata { + columns: DataViewMetadataColumn[]; + + /** The metadata repetition objects. */ + objects?: DataViewObjects; + + /** When defined, describes whether the DataView contains just a segment of the complete data set. */ + segment?: DataViewSegmentMetadata; + } + + export interface DataViewMetadataColumn { + /** The user-facing display name of the column. */ + displayName: string; + + /** The query name the source column in the query. */ + queryName?: string; + + /** The format string of the column. */ + format?: string; // TODO: Deprecate this, and populate format string through objects instead. + + /** Data type information for the column. */ + type?: ValueTypeDescriptor; + + /** Indicates that this column is a measure (aggregate) value. */ + isMeasure?: boolean; + + /** The position of the column in the select statement. */ + index?: number; + + /** The properties that this column provides to the visualization. */ + roles?: { [name: string]: boolean }; + + /** The metadata repetition objects. */ + objects?: DataViewObjects; + + /** The name of the containing group. */ + groupName?: string; + + /** The sort direction of this column. */ + sort?: SortDirection; + + /** The KPI metadata to use to convert a numeric status value into its visual representation. */ + kpi?: DataViewKpiColumnMetadata; + + /** Indicates that aggregates should not be computed across groups with different values of this column. */ + discourageAggregationAcrossGroups?: boolean; + + /** The aggregates computed for this column, if any. */ + aggregates?: DataViewColumnAggregates; + + /** The SQExpr this column represents. */ + expr?: data.ISQExpr; + } + + export interface DataViewSegmentMetadata { + } + + export interface DataViewColumnAggregates { + subtotal?: PrimitiveValue; + max?: PrimitiveValue; + min?: PrimitiveValue; + count?: number; + percentiles?: DataViewColumnPercentileAggregate[]; + + /** Client-computed maximum value for a column. */ + maxLocal?: PrimitiveValue; + + /** Client-computed maximum value for a column. */ + minLocal?: PrimitiveValue; + } + + export interface DataViewColumnPercentileAggregate { + exclusive?: boolean; + k: number; + value: PrimitiveValue; + } + + export interface DataViewCategorical { + categories?: DataViewCategoryColumn[]; + values?: DataViewValueColumns; + } + + export interface DataViewCategoricalColumn { + source: DataViewMetadataColumn; + values: any[]; + + /** The data repetition objects. */ + objects?: DataViewObjects[]; + } + + export interface DataViewValueColumns extends Array { + /** Returns an array that groups the columns in this group together. */ + grouped(): DataViewValueColumnGroup[]; + + /** The set of expressions that define the identity for instances of the value group. This must match items in the DataViewScopeIdentity in the grouped items result. */ + identityFields?: data.ISQExpr[]; + + source?: DataViewMetadataColumn; + } + + export interface DataViewValueColumnGroup { + values: DataViewValueColumn[]; + identity?: DataViewScopeIdentity; + + /** The data repetition objects. */ + objects?: DataViewObjects; + + name?: string; + } + + export interface DataViewValueColumn extends DataViewCategoricalColumn { + highlights?: any[]; + identity?: DataViewScopeIdentity; + } + + // NOTE: The following is needed for backwards compatibility and should be deprecated. Callers should use + // DataViewMetadataColumn.aggregates instead. + export interface DataViewValueColumn extends DataViewColumnAggregates { + } + + export interface DataViewCategoryColumn extends DataViewCategoricalColumn { + identity?: DataViewScopeIdentity[]; + + /** The set of expressions that define the identity for instances of the category. This must match items in the DataViewScopeIdentity in the identity. */ + identityFields?: data.ISQExpr[]; + } + + export interface DataViewSingle { + value: any; + } + + export interface DataViewTree { + root: DataViewTreeNode; + } + + export interface DataViewTreeNode { + name?: string; + + /** + * When used under the context of DataView.tree, this value is one of the elements in the values property. + * + * When used under the context of DataView.matrix, this property is the value of the particular + * group instance represented by this node (e.g. In a grouping on Year, a node can have value == 2016). + * + * DEPRECATED for usage under the context of DataView.matrix: This property is deprecated for objects + * that conform to the DataViewMatrixNode interface (which extends DataViewTreeNode). + * New visuals code should consume the new property levelValues on DataViewMatrixNode instead. + * If this node represents a composite group node in matrix, this property will be undefined. + */ + value?: any; + + /** + * This property contains all the values in this node. + * The key of each of the key-value-pair in this dictionary is the position of the column in the + * select statement to which the value belongs. + */ + values?: { [id: number]: DataViewTreeNodeValue }; + + children?: DataViewTreeNode[]; + identity?: DataViewScopeIdentity; + + /** The data repetition objects. */ + objects?: DataViewObjects; + + /** The set of expressions that define the identity for the child nodes. This must match items in the DataViewScopeIdentity of those nodes. */ + childIdentityFields?: data.ISQExpr[]; + } + + export interface DataViewTreeNodeValue { + value?: any; + } + + export interface DataViewTreeNodeMeasureValue extends DataViewTreeNodeValue, DataViewColumnAggregates { + highlight?: any; + } + + export interface DataViewTreeNodeGroupValue extends DataViewTreeNodeValue { + count?: any; + } + + export interface DataViewTable { + columns: DataViewMetadataColumn[]; + + identity?: DataViewScopeIdentity[]; + + /** The set of expressions that define the identity for rows of the table. This must match items in the DataViewScopeIdentity in the identity. */ + identityFields?: data.ISQExpr[]; + + rows?: DataViewTableRow[]; + + totals?: any[]; + } + + export interface DataViewTableRow extends Array { + /** The metadata repetition objects. */ + objects?: DataViewObjects[]; + } + + export interface DataViewMatrix { + rows: DataViewHierarchy; + columns: DataViewHierarchy; + valueSources: DataViewMetadataColumn[]; + } + + export interface DataViewMatrixNode extends DataViewTreeNode { + /** Indicates the level this node is on. Zero indicates the outermost children (root node level is undefined). */ + level?: number; + + children?: DataViewMatrixNode[]; + + + values?: { [id: number]: DataViewMatrixNodeValue }; + + /** + * Indicates the source metadata index on the node's level. Its value is 0 if omitted. + * + * DEPRECATED: This property is deprecated and exists for backward-compatibility only. + * New visuals code should consume the new property levelSourceIndex on DataViewMatrixGroupValue instead. + */ + levelSourceIndex?: number; + + /** + * The values of the particular group instance represented by this node. + * This array property would contain more than one element in a composite group + * (e.g. Year == 2016 and Month == 'January'). + */ + levelValues?: DataViewMatrixGroupValue[]; + + /** Indicates whether or not the node is a subtotal node. Its value is false if omitted. */ + isSubtotal?: boolean; + } + + /** + * Represents a value at a particular level of a matrix's rows or columns hierarchy. + * In the hierarchy level node is an instance of a composite group, this object will + * be one of multiple values + */ + export interface DataViewMatrixGroupValue extends DataViewTreeNodeValue { + /** + * Indicates the index of the corresponding column for this group level value + * (held by DataViewHierarchyLevel.sources). + * + * @example + * // For example, to get the source column metadata of each level value at a particular row hierarchy node: + * let matrixRowsHierarchy: DataViewHierarchy = dataView.matrix.rows; + * let targetRowsHierarchyNode = matrixRowsHierarchy.root.children[0]; + * // Use the DataViewMatrixNode.level property to get the corresponding DataViewHierarchyLevel... + * let targetRowsHierarchyLevel: DataViewHierarchyLevel = matrixRows.levels[targetRowsHierarchyNode.level]; + * for (let levelValue in rowsRootNode.levelValues) { + * // columnMetadata is the source column for the particular levelValue.value in this loop iteration + * let columnMetadata: DataViewMetadataColumn = + * targetRowsHierarchyLevel.sources[levelValue.levelSourceIndex]; + * } + */ + levelSourceIndex: number; + } + + /** Represents a value at the matrix intersection, used in the values property on DataViewMatrixNode (inherited from DataViewTreeNode). */ + export interface DataViewMatrixNodeValue extends DataViewTreeNodeValue { + highlight?: any; + + /** Indicates the index of the corresponding measure (held by DataViewMatrix.valueSources). Its value is 0 if omitted. */ + valueSourceIndex?: number; + } + + export interface DataViewHierarchy { + root: DataViewMatrixNode; + levels: DataViewHierarchyLevel[]; + } + + export interface DataViewHierarchyLevel { + sources: DataViewMetadataColumn[]; + } + + export interface DataViewKpiColumnMetadata { + graphic: string; + + // When false, five state KPIs are in: { -2, -1, 0, 1, 2 }. + // When true, five state KPIs are in: { -1, -0.5, 0, 0.5, 1 }. + normalizedFiveStateKpiRange?: boolean; + } + + export interface DataViewScriptResultData { + imageBase64: string; + } +} + + + +declare module powerbi { + export interface DataViewMapping { + /** + * Defines set of conditions, at least one of which must be satisfied for this mapping to be used. + * Any roles not specified in the condition accept any number of items. + */ + conditions?: DataViewMappingCondition[]; + requiredProperties?: DataViewObjectPropertyIdentifier[]; + + categorical?: DataViewCategoricalMapping; + table?: DataViewTableMapping; + single?: DataViewSingleMapping; + tree?: DataViewTreeMapping; + matrix?: DataViewMatrixMapping; + scriptResult?: DataViewScriptResultMapping; + usage?: DataViewMappingUsage; + } + + /** Describes whether a particular mapping is fits the set of projections. */ + export interface DataViewMappingCondition { + [dataRole: string]: RoleCondition; + } + + /** Describes a mapping which supports a data volume level. */ + export interface HasDataVolume { + dataVolume?: number; + } + + export interface DataViewCategoricalMapping extends HasDataVolume, HasReductionAlgorithm { + categories?: DataViewRoleMappingWithReduction | DataViewListRoleMappingWithReduction; + values?: DataViewRoleMapping | DataViewGroupedRoleMapping | DataViewListRoleMapping; + + /** Specifies a constraint on the number of data rows supported by the visual. */ + rowCount?: AcceptabilityNumberRange; + + /** Indicates whether the data rows include empty groups */ + includeEmptyGroups?: boolean; + } + + export interface DataViewSingleMapping { + /** Indicates the role which is bound to this structure. */ + role: string; + } + + export interface DataViewTableMapping extends HasDataVolume { + rows: DataViewRoleMappingWithReduction | DataViewListRoleMappingWithReduction; + + /** Specifies a constraint on the number of data rows supported by the visual. */ + rowCount?: AcceptabilityNumberRange; + } + + export interface DataViewTreeMapping extends HasDataVolume { + nodes?: DataViewRoleForMappingWithReduction; + values?: DataViewRoleForMapping; + + /** Specifies a constraint on the depth of the tree supported by the visual. */ + depth?: AcceptabilityNumberRange; + } + + export interface DataViewMatrixMapping extends HasDataVolume { + rows?: DataViewRoleForMappingWithReduction | DataViewListRoleMappingWithReduction; + columns?: DataViewRoleForMappingWithReduction; + values?: DataViewRoleForMapping | DataViewListRoleMapping; + } + + + export type DataViewRoleMapping = DataViewRoleBindMapping | DataViewRoleForMapping; + + + + export interface DataViewRoleBindMapping { + /** + * Indicates evaluation of a single-valued data role. + * Equivalent to for, without support for multiple items. + */ + bind: { + to: string; + + /** Requests aggregates for the visual. When specified, only the aggregates are requested. */ + aggregates?: DataViewMappingRoleProjectionAggregates; + }; + } + + export interface DataViewRoleForMapping { + /** Indicates iteration of the in data role, as an array. */ + for: { + in: string; + }; + } + + export type DataViewRoleMappingWithReduction = DataViewRoleBindMappingWithReduction | DataViewRoleForMappingWithReduction; + + export interface DataViewRoleBindMappingWithReduction extends DataViewRoleBindMapping, HasReductionAlgorithm { + } + + export interface DataViewRoleForMappingWithReduction extends DataViewRoleForMapping, HasReductionAlgorithm { + } + + export interface DataViewGroupedRoleMapping { + group: { + by: string; + select: DataViewRoleMapping[]; + dataReductionAlgorithm?: ReductionAlgorithm; + }; + } + + export interface DataViewListRoleMapping { + select: DataViewRoleMapping[]; + } + + export interface DataViewListRoleMappingWithReduction extends DataViewListRoleMapping, HasReductionAlgorithm { + } + + export interface HasReductionAlgorithm { + dataReductionAlgorithm?: ReductionAlgorithm; + } + + /** Describes how to reduce the amount of data exposed to the visual. */ + export interface ReductionAlgorithm { + top?: DataReductionTop; + bottom?: DataReductionBottom; + sample?: DataReductionSample; + window?: DataReductionWindow; + } + + /** Reduce the data to the Top(count) items. */ + export interface DataReductionTop { + count?: number; + } + + /** Reduce the data to the Bottom count items. */ + export interface DataReductionBottom { + count?: number; + } + + /** Reduce the data using a simple Sample of count items. */ + export interface DataReductionSample { + count?: number; + } + + /** Allow the data to be loaded one window, containing count items, at a time. */ + export interface DataReductionWindow { + count?: number; + } + + export interface AcceptabilityNumberRange { + /** Specifies a preferred range of values for the constraint. */ + preferred?: NumberRange; + + /** Specifies a supported range of values for the constraint. Defaults to preferred if not specified. */ + supported?: NumberRange; + } + + /** Defines the acceptable values of a number. */ + export interface NumberRange { + min?: number; + max?: number; + } + + export interface DataViewMappingScriptDefinition { + source: DataViewObjectPropertyIdentifier; + provider: DataViewObjectPropertyIdentifier; + imageFormat?: string; + } + + export interface DataViewScriptResultMapping { + dataInput: DataViewMapping; + script: DataViewMappingScriptDefinition; + } + + /** Defines how the mapping will be used. The set of objects in this interface can modify the usage. */ + export interface DataViewMappingUsage { + regression: { + [propertyName: string]: DataViewObjectPropertyIdentifier; + }; + } + + export interface DataViewMappingRoleProjectionAggregates { + min?: boolean; + max?: boolean; + } +} + +declare module powerbi { + /** Represents evaluated, named, custom objects in a DataView. */ + export interface DataViewObjects { + [name: string]: DataViewObject | DataViewObjectMap; + } + + /** Represents an object (name-value pairs) in a DataView. */ + export interface DataViewObject { + [propertyName: string]: DataViewPropertyValue; + } + + export interface DataViewObjectWithId { + id: string; + object: DataViewObject; + } + + export interface DataViewObjectPropertyIdentifier { + objectName: string; + propertyName: string; + } + + export type DataViewObjectMap = DataViewObjectWithId[]; + + export type DataViewPropertyValue = PrimitiveValue | StructuralObjectValue; +} + +declare module powerbi.data { + export interface DataViewObjectDescriptors { + /** Defines general properties for a visualization. */ + general?: DataViewObjectDescriptor; + + [objectName: string]: DataViewObjectDescriptor; + } + + /** Defines a logical object in a visualization. */ + export interface DataViewObjectDescriptor { + displayName?: DisplayNameGetter; + description?: DisplayNameGetter; + properties: DataViewObjectPropertyDescriptors; + } + + export interface DataViewObjectPropertyDescriptors { + [propertyName: string]: DataViewObjectPropertyDescriptor; + } + + /** Defines a property of a DataViewObjectDefinition. */ + export interface DataViewObjectPropertyDescriptor { + displayName?: DisplayNameGetter; + description?: DisplayNameGetter; + placeHolderText?: DisplayNameGetter; + type: DataViewObjectPropertyTypeDescriptor; + rule?: DataViewObjectPropertyRuleDescriptor; + + /** Indicates whether the Format Painter should ignore this property. */ + suppressFormatPainterCopy?: boolean; + } + + export type DataViewObjectPropertyTypeDescriptor = ValueTypeDescriptor | StructuralTypeDescriptor; + + export interface DataViewObjectPropertyRuleDescriptor { + /** For rule typed properties, defines the input visual role name. */ + inputRole?: string; + + /** Defines the output for rule-typed properties. */ + output?: DataViewObjectPropertyRuleOutputDescriptor; + } + + export interface DataViewObjectPropertyRuleOutputDescriptor { + /** Name of the target property for rule output. */ + property: string; + + /** Names roles that define the selector for the output properties. */ + selector: string[]; + } + +} + +declare module powerbi.data { + /** Defines a match against all instances of given roles. */ + export interface DataViewRoleWildcard { + roles: string[]; + key: string; + } +} + +declare module powerbi { + /** Encapsulates the identity of a data scope in a DataView. */ + export interface DataViewScopeIdentity { + /** Predicate expression that identifies the scope. */ + expr: data.ISQExpr; + + /** Key string that identifies the DataViewScopeIdentity to a string, which can be used for equality comparison. */ + key: string; + } +} + +declare module powerbi.data { + /** Defines a match against all instances of a given DataView scope. */ + export interface DataViewScopeWildcard { + exprs: ISQExpr[]; + key: string; + } +} + +declare module powerbi.data { + import IStringResourceProvider = jsCommon.IStringResourceProvider; + + export type DisplayNameGetter = ((resourceProvider: IStringResourceProvider) => string) | string; +} + +declare module powerbi.data { + export interface ScriptInputColumn { + /** The queryName of the corresponding Select from the associated SemanticQuery providing the data for this column. */ + QueryName: string; + + /** The name of this column expected by the script. */ + Name: string; + } + + export interface ScriptInput { + VariableName?: string; + Columns?: ScriptInputColumn[]; + } +} + +declare module powerbi.data { + /** Defines a selector for content, including data-, metadata, and user-defined repetition. */ + export interface Selector { + /** Data-bound repetition selection. */ + data?: DataRepetitionSelector[]; + + /** Metadata-bound repetition selection. Refers to a DataViewMetadataColumn queryName. */ + metadata?: string; + + /** User-defined repetition selection. */ + id?: string; + } + + export type DataRepetitionSelector = DataViewScopeIdentity | DataViewScopeWildcard | DataViewRoleWildcard; +} + +declare module powerbi.data { + //intentionally blank interfaces since this is not part of the public API + + export interface ISemanticFilter { } + + export interface ISQExpr { } + + export interface ISQConstantExpr extends ISQExpr { } + +} + +declare module powerbi { + export interface IViewport { + height: number; + width: number; + } +} + +declare module powerbi { + import DisplayNameGetter = powerbi.data.DisplayNameGetter; + + /** Defines the data roles understood by the IVisual. */ + export interface VisualDataRole { + /** Unique name for the VisualDataRole. */ + name: string; + + /** Indicates the kind of role. This value is used to build user interfaces, such as a field well. */ + kind: VisualDataRoleKind; + + displayName?: DisplayNameGetter; + + /** The tooltip text */ + description?: DisplayNameGetter; + + /** Indicates the preferred ValueTypes to be used in this data role. This is used by authoring tools when adding fields into the visual. */ + preferredTypes?: ValueTypeDescriptor[]; + + /** Indicates the required ValueTypes for this data role. Any values which do not match one of the ValueTypes specified will be null'd out */ + requiredTypes?: ValueTypeDescriptor[]; + + /** Indicates the cartesian role for the visual role */ + cartesianKind?: CartesianRoleKind; + + /** Indicates the join predicate behavior of items in this role. */ + joinPredicate?: JoinPredicateBehavior; + } + + export interface RoleCondition extends NumberRange { + kind?: VisualDataRoleKind; + } +} + +declare module powerbi { + + /** + * Interface that provides scripted access to geographical location information associated with the hosting device + * The Interface is similar to W3 Geolocation API Specification {@link https://dev.w3.org/geo/api/spec-source.html} + */ + export interface IGeolocation { + /** + * Request repeated updates + * + * @param successCallback invoked when current location successfully obtained + * @param errorCallback invoked when attempt to obtain the current location fails + * + * @return a number value that uniquely identifies a watch operation + */ + watchPosition(successCallback: IPositionCallback, errorCallback?: IPositionErrorCallback): number; + /** + * Cancel the updates + * + * @param watchId a number returned from {@link IGeolocation#watchPosition} + */ + clearWatch(watchId: number): void; + /** + * One-shot position request. + * + * @param successCallback invoked when current location successfully obtained + * @param errorCallback invoked when attempt to obtain the current location fails + */ + getCurrentPosition(successCallback: IPositionCallback, errorCallback?: IPositionErrorCallback): void; + } + + export interface IPositionCallback { + (position: Position): void; + } + + export interface IPositionErrorCallback { + (error: PositionError): void; + } +} + +declare module powerbi { + export interface DefaultValueDefinition { + value: data.ISQConstantExpr; + identityFieldsValues?: data.ISQConstantExpr[]; + } + + export interface DefaultValueTypeDescriptor { + defaultValue: boolean; + } +} + +declare module powerbi { + import DisplayNameGetter = powerbi.data.DisplayNameGetter; + + export type EnumMemberValue = string | number; + + export interface IEnumMember { + value: EnumMemberValue; + displayName: DisplayNameGetter; + } + + /** Defines a custom enumeration data type, and its values. */ + export interface IEnumType { + /** Gets the members of the enumeration, limited to the validMembers, if appropriate. */ + members(validMembers?: EnumMemberValue[]): IEnumMember[]; + } + +} + +declare module powerbi { + export interface Fill { + solid?: { + color?: string; + }; + gradient?: { + startColor?: string; + endColor?: string; + }; + pattern?: { + patternKind?: string; + color?: string; + }; + } + + export interface FillTypeDescriptor { + solid?: { + color?: FillSolidColorTypeDescriptor; + }; + gradient?: { + startColor?: boolean; + endColor?: boolean; + }; + pattern?: { + patternKind?: boolean; + color?: boolean; + }; + } + + export type FillSolidColorTypeDescriptor = boolean | FillSolidColorAdvancedTypeDescriptor; + + export interface FillSolidColorAdvancedTypeDescriptor { + /** Indicates whether the color value may be nullable, and a 'no fill' option is appropriate. */ + nullable: boolean; + } +} + +declare module powerbi { + export interface FillRule extends FillRuleGeneric { + } + + export interface FillRuleTypeDescriptor { + } + + export interface FillRuleGeneric { + linearGradient2?: LinearGradient2Generic; + linearGradient3?: LinearGradient3Generic; + + // stepped2? + // ... + } + + export interface LinearGradient2Generic { + max: RuleColorStopGeneric; + min: RuleColorStopGeneric; + } + + export interface LinearGradient3Generic { + max: RuleColorStopGeneric; + mid: RuleColorStopGeneric; + min: RuleColorStopGeneric; + } + + export interface RuleColorStopGeneric { + color: TColor; + value?: TValue; + } +} + +declare module powerbi { + export interface FilterTypeDescriptor { + selfFilter?: boolean; + } +} + +declare module powerbi { + export type GeoJson = GeoJsonDefinitionGeneric; + + export interface GeoJsonDefinitionGeneric { + type: T; + name: T; + content: T; + } + + export interface GeoJsonTypeDescriptor { } +} + +declare module powerbi { + export type ImageValue = ImageDefinitionGeneric; + + export interface ImageDefinitionGeneric { + name: T; + url: T; + scaling?: T; + } + + export interface ImageTypeDescriptor { } +} + +declare module powerbi { + export type Paragraphs = Paragraph[]; + export interface Paragraph { + horizontalTextAlignment?: string; + textRuns: TextRun[]; + } + + export interface ParagraphsTypeDescriptor { + } + + export interface TextRunStyle { + fontFamily?: string; + fontSize?: string; + fontStyle?: string; + fontWeight?: string; + textDecoration?: string; + } + + export interface TextRun { + textStyle?: TextRunStyle; + url?: string; + value: string; + } +} + +declare module powerbi { + import SemanticFilter = data.ISemanticFilter; + + /** Defines instances of structural types. */ + export type StructuralObjectValue = + Fill | + FillRule | + SemanticFilter | + DefaultValueDefinition | + ImageValue | + Paragraphs | + GeoJson; + + /** Describes a structural type in the client type system. Leaf properties should use ValueType. */ + export interface StructuralTypeDescriptor { + fill?: FillTypeDescriptor; + fillRule?: FillRuleTypeDescriptor; + filter?: FilterTypeDescriptor; + expression?: DefaultValueTypeDescriptor; + image?: ImageTypeDescriptor; + paragraphs?: ParagraphsTypeDescriptor; + geoJson?: GeoJsonTypeDescriptor; + + //border?: BorderTypeDescriptor; + //etc. + } +} + +declare module powerbi { + /** Describes a data value type in the client type system. Can be used to get a concrete ValueType instance. */ + export interface ValueTypeDescriptor { + // Simplified primitive types + text?: boolean; + numeric?: boolean; + integer?: boolean; + bool?: boolean; + dateTime?: boolean; + duration?: boolean; + binary?: boolean; + none?: boolean; //TODO: 5005022 remove none type when we introduce property categories. + + // Extended types + temporal?: TemporalTypeDescriptor; + geography?: GeographyTypeDescriptor; + misc?: MiscellaneousTypeDescriptor; + formatting?: FormattingTypeDescriptor; + enumeration?: IEnumType; + scripting?: ScriptTypeDescriptor; + operations?: OperationalTypeDescriptor; + } + + export interface ScriptTypeDescriptor { + source?: boolean; + } + + export interface TemporalTypeDescriptor { + year?: boolean; + month?: boolean; + } + + export interface GeographyTypeDescriptor { + address?: boolean; + city?: boolean; + continent?: boolean; + country?: boolean; + county?: boolean; + region?: boolean; + postalCode?: boolean; + stateOrProvince?: boolean; + place?: boolean; + latitude?: boolean; + longitude?: boolean; + } + + export interface MiscellaneousTypeDescriptor { + image?: boolean; + imageUrl?: boolean; + webUrl?: boolean; + barcode?: boolean; + } + + export interface FormattingTypeDescriptor { + color?: boolean; + formatString?: boolean; + alignment?: boolean; + labelDisplayUnits?: boolean; + fontSize?: boolean; + labelDensity?: boolean; + } + + export interface OperationalTypeDescriptor { + searchEnabled?: boolean; + } + + /** Describes instances of value type objects. */ + export type PrimitiveValue = string | number | boolean | Date; +} + +declare module powerbi { + export interface IVisualStyle { + colorPalette: IColorPalette; + isHighContrast: boolean; + titleText: ITextStyle; + subTitleText: ITextStyle; + labelText: ITextStyle; + // TODO 4486317: This is a host-specific property that should be exposed through DataViewObjects. + maxMarginFactor?: number; + } + + export interface ITextStyle extends IStyleInfo { + fontFace?: string; + fontSize?: string; + fontWeight?: string; + color: IColorInfo; + } + + export interface IColorPalette { + background?: IColorInfo; + foreground?: IColorInfo; + + positive?: IColorInfo; + neutral?: IColorInfo; + negative?: IColorInfo; + separator?: IColorInfo; + selection?: IColorInfo; + + dataColors: IDataColorPalette; + } + + export interface IDataColorPalette { + /** Gets the color scale associated with the given key. */ + getColorScaleByKey(scaleKey: string): IColorScale; + + /** Gets a fresh color scale with no colors allocated. */ + getNewColorScale(): IColorScale; + + /** Gets the nth color in the palette. */ + getColorByIndex(index: number): IColorInfo; + + /** + * Gets the set of sentiment colors used for visuals such as KPIs + * Note: This is only a temporary API so that we can have reasonable color schemes for KPIs + * and gauges until the conditional formatting feature is implemented. + */ + getSentimentColors(): IColorInfo[]; + + getBasePickerColors(): IColorInfo[]; + + /** Gets all the colors for the color palette **/ + getAllColors?(): IColorInfo[]; + } + + export interface IColorScale { + /** Gets the color associated with the given key. */ + getColor(key: any): IColorInfo; + + /** + * Clears the current scale, but rotates the colors such that the first color allocated will + * the be first color that would have been allocated before clearing the scale. + */ + clearAndRotateScale(): void; + + /** Returns a copy of the current scale. */ + clone(): IColorScale; + + getDomain(): any[]; + } + + export interface IColorInfo extends IStyleInfo { + value: string; + } + + export interface IStyleInfo { + className?: string; + } +} + +declare module powerbi { + import DataViewObjectDescriptor = powerbi.data.DataViewObjectDescriptor; + import DataViewObjectDescriptors = powerbi.data.DataViewObjectDescriptors; + import Selector = powerbi.data.Selector; + import IPoint = powerbi.visuals.IPoint; + import ISemanticFilter = powerbi.data.ISemanticFilter; + import ISQExpr = powerbi.data.ISQExpr; + import IStringResourceProvider = jsCommon.IStringResourceProvider; + import IRect = powerbi.visuals.IRect; + + /** Parameters available to a CustomizeQueryMethod */ + export interface CustomizeQueryOptions { + /** + * The data view mapping for this visual with some additional information. CustomizeQueryMethod implementations + * are expected to edit this in-place. + */ + dataViewMappings: data.CompiledDataViewMapping[]; + + /** + * Visual should prefer to request a higher volume of data. + */ + preferHigherDataVolume?: boolean; + + /** + * Whether the load more data feature (paging of data) for Cartesian charts should be enabled. + */ + cartesianLoadMoreEnabled?: boolean; + } + + /** Parameters available to a sortable visual candidate */ + export interface VisualSortableOptions { + + dataViewMappings: data.CompiledDataViewMapping[]; + } + + /** An imperative way for a visual to influence query generation beyond just its declared capabilities. */ + export interface CustomizeQueryMethod { + (options: CustomizeQueryOptions): void; + } + + /** Defines the visual filtering capability for a particular filter kind. */ + export interface VisualFilterMapping { + /** Specifies what data roles are used to control the filter semantics for this filter kind. */ + targetRoles: string[]; + } + + /** + * Defines the visual filtering capabilities for various filter kinds. + * By default all visuals support attribute filters and measure filters in their innermost scope. + */ + export interface VisualFilterMappings { + measureFilter?: VisualFilterMapping; + } + + /** Defines the capabilities of an IVisual. */ + export interface VisualCapabilities { + /** Defines what roles the visual expects, and how those roles should be populated. This is useful for visual generation/editing. */ + dataRoles?: VisualDataRole[]; + + /** Defines the set of objects supported by this IVisual. */ + objects?: DataViewObjectDescriptors; + + /** Defines how roles that the visual understands map to the DataView. This is useful for query generation. */ + dataViewMappings?: DataViewMapping[]; + + /** Defines how filters are understood by the visual. This is used by query generation */ + filterMappings?: VisualFilterMappings; + + /** Indicates whether cross-highlight is supported by the visual. This is useful for query generation. */ + supportsHighlight?: boolean; + + /** Indicates whether the visual uses onSelected function for data selections. Default is true. */ + supportsSelection?: boolean; + + /** Indicates whether sorting is supported by the visual. This is useful for query generation */ + sorting?: VisualSortingCapabilities; + + /** Indicates whether a default title should be displayed. Visuals with self-describing layout can omit this. */ + suppressDefaultTitle?: boolean; + + /** Indicates whether a default padding should be applied. */ + suppressDefaultPadding?: boolean; + + /** Indicates whether drilling is supported by the visual. */ + drilldown?: VisualDrillCapabilities; + + /** Indicates whether rotating is supported by the visual. */ + canRotate?: boolean; + + /** Indicates whether showing the data underlying this visual would be helpful. Visuals that already show raw data can specify this. */ + disableVisualDetails?: boolean; + + /** Indicates whether focus mode is supported for the visual. Visuals that would not benefit from focus mode (such as non-data-bound ones) can set it to true. */ + disableFocusMode?: boolean; + } + + /** Defines the visual sorting capability. */ + export interface VisualSortingCapabilities { + /** When specified, indicates that the IVisual wants default sorting behavior. */ + default?: {}; + + /** When specified, indicates that the IVisual wants to control sort interactivity. */ + custom?: {}; + + /** When specified, indicates sorting that is inherently implied by the IVisual. This is useful to automatically sort. */ + implicit?: VisualImplicitSorting; + } + + /** Defines the visual's drill capability. */ + export interface VisualDrillCapabilities { + /** Returns the drillable role names for this visual **/ + roles?: string[]; + } + + /** Defines implied sorting behaviour for an IVisual. */ + export interface VisualImplicitSorting { + clauses: VisualImplicitSortingClause[]; + } + + export interface VisualImplicitSortingClause { + role: string; + direction: SortDirection; + } + + export interface VisualUpdateOptions { + viewport: IViewport; + dataViews: DataView[]; + suppressAnimations?: boolean; + viewMode?: ViewMode; + resizeMode?: ResizeMode; + type?: VisualUpdateType; + /** Indicates what type of update has been performed on the data. + The default operation kind is Create.*/ + operationKind?: VisualDataChangeOperationKind; + } + + export interface VisualDataChangedOptions { + dataViews: DataView[]; + + /** Optionally prevent animation transitions */ + suppressAnimations?: boolean; + + /** Indicates what type of update has been performed on the data. + The default operation kind is Create.*/ + operationKind?: VisualDataChangeOperationKind; + } + + export interface CustomSortEventArgs { + sortDescriptors: SortableFieldDescriptor[]; + } + + export interface SortableFieldDescriptor { + queryName: string; + sortDirection?: SortDirection; + } + + export interface IVisualErrorMessage { + message: string; + title: string; + detail: string; + } + + export interface IVisualWarning { + code: string; + getMessages(resourceProvider: IStringResourceProvider): IVisualErrorMessage; + } + + /** Animation options for visuals. */ + export interface AnimationOptions { + /** Indicates whether all transition frames should be flushed immediately, effectively "disabling" any visual transitions. */ + transitionImmediate: boolean; + } + + /** Interactivity options for visuals. */ + export interface InteractivityOptions { + /** Indicates that dragging of data points should be permitted. */ + dragDataPoint?: boolean; + + /** Indicates that data points should be selectable. */ + selection?: boolean; + + /** Indicates that the chart and the legend are interactive */ + isInteractiveLegend?: boolean; + + /** Indicates overflow behavior. Values are CSS oveflow strings */ + overflow?: string; + } + + export interface VisualDragPayload extends DragPayload { + data?: Selector; + field?: {}; + } + + export interface DragEventArgs { + event: DragEvent; + data: VisualDragPayload; + } + + /** Defines geocoding services. */ + export interface GeocodeOptions { + /** promise that should abort the request when resolved */ + timeout?: IPromise; + } + + export interface IGeocoder { + geocode(query: string, category?: string, options?: GeocodeOptions): IPromise; + geocodeBoundary(latitude: number, longitude: number, category: string, levelOfDetail?: number, maxGeoData?: number, options?: GeocodeOptions): IPromise; + geocodePoint(latitude: number, longitude: number, options?: GeocodeOptions): IPromise; + + /** returns data immediately if it is locally available (e.g. in cache), null if not in cache */ + tryGeocodeImmediate(query: string, category?: string): IGeocodeCoordinate; + tryGeocodeBoundaryImmediate(latitude: number, longitude: number, category: string, levelOfDetail?: number, maxGeoData?: number): IGeocodeBoundaryCoordinate; + } + + export interface IGeocodeCoordinate { + latitude: number; + longitude: number; + } + + export interface IGeocodeBoundaryCoordinate { + latitude?: number; + longitude?: number; + locations?: IGeocodeBoundaryPolygon[]; // one location can have multiple boundary polygons + } + + export interface IGeocodeResource extends IGeocodeCoordinate { + addressLine: string; + locality: string; + neighborhood: string; + adminDistrict: string; + adminDistrict2: string; + formattedAddress: string; + postalCode: string; + countryRegionIso2: string; + countryRegion: string; + landmark: string; + name: string; + } + + export interface IGeocodeBoundaryPolygon { + nativeBing: string; + + /** array of lat/long pairs as [lat1, long1, lat2, long2,...] */ + geographic?: Float64Array; + + /** array of absolute pixel position pairs [x1,y1,x2,y2,...]. It can be used by the client for cache the data. */ + absolute?: Float64Array; + absoluteBounds?: IRect; + + /** string of absolute pixel position pairs "x1 y1 x2 y2...". It can be used by the client for cache the data. */ + absoluteString?: string; + } + + export interface SelectorForColumn { + [queryName: string]: data.DataRepetitionSelector; + } + + export interface SelectorsByColumn { + /** Data-bound repetition selection. */ + dataMap?: SelectorForColumn; + + /** Metadata-bound repetition selection. Refers to a DataViewMetadataColumn queryName. */ + metadata?: string; + + /** User-defined repetition selection. */ + id?: string; + } + + // TODO: Consolidate these two into one object and add a method to transform SelectorsByColumn[] into Selector[] for components that need that structure + export interface SelectEventArgs { + data: Selector[]; + data2?: SelectorsByColumn[]; + } + + export interface ContextMenuArgs { + data: SelectorsByColumn[]; + + /** Absolute coordinates for the top-left anchor of the context menu. */ + position: IPoint; + } + + export interface SelectObjectEventArgs { + object: DataViewObjectDescriptor; + } + + export interface FilterAnalyzerOptions { + dataView: DataView; + + /** The DataViewObjectPropertyIdentifier for default value */ + defaultValuePropertyId: DataViewObjectPropertyIdentifier; + + /** The filter that will be analyzed */ + filter: ISemanticFilter; + + /** The field SQExprs used in the filter */ + fieldSQExprs: ISQExpr[]; + } + + export interface AnalyzedFilter { + /** The default value of the slicer selected item and it can be undefined if there is no default value */ + defaultValue?: DefaultValueDefinition; + + /** Indicates the filter has Not condition. */ + isNotFilter: boolean; + + /** The selected filter values. */ + selectedIdentities: DataViewScopeIdentity[]; + + /** The filter after analyzed. It will be the default filter if it has defaultValue and the pre-analyzed filter is undefined. */ + filter: ISemanticFilter; + } + + export interface DisplayNameIdentityPair { + displayName: string; + identity: DataViewScopeIdentity; + } +} + + + + +declare module powerbi { + import Selector = powerbi.data.Selector; + + export interface VisualObjectInstance { + /** The name of the object (as defined in VisualCapabilities). */ + objectName: string; + + /** A display name for the object instance. */ + displayName?: string; + + /** The set of property values for this object. Some of these properties may be defaults provided by the IVisual. */ + properties: { + [propertyName: string]: DataViewPropertyValue; + }; + + /** The selector that identifies this object. */ + selector: Selector; + + /** Defines the constrained set of valid values for a property. */ + validValues?: { + [propertyName: string]: string[]; + }; + + /** (Optional) VisualObjectInstanceEnumeration category index. */ + containerIdx?: number; + } + + export type VisualObjectInstanceEnumeration = VisualObjectInstance[] | VisualObjectInstanceEnumerationObject; + + export interface VisualObjectInstanceEnumerationObject { + /** The visual object instances. */ + instances: VisualObjectInstance[]; + + /** Defines a set of containers for related object instances. */ + containers?: VisualObjectInstanceContainer[]; + } + + export interface VisualObjectInstanceContainer { + displayName: data.DisplayNameGetter; + } + + export interface VisualObjectInstancesToPersist { + /** Instances which should be merged with existing instances. */ + merge?: VisualObjectInstance[]; + + /** Instances which should replace existing instances. */ + replace?: VisualObjectInstance[]; + + /** Instances which should be deleted from the existing instances. */ + remove?: VisualObjectInstance[]; + } + + export interface EnumerateVisualObjectInstancesOptions { + objectName: string; + } +} + +declare module powerbi { + import Selector = powerbi.data.Selector; + + export interface VisualObjectRepetition { + /** The selector that identifies the objects. */ + selector: Selector; + + /** The set of repetition descriptors for this object. */ + objects: { + [objectName: string]: DataViewRepetitionObjectDescriptor; + }; + } + + export interface DataViewRepetitionObjectDescriptor { + /** Properties used for formatting (e.g., Conditional Formatting). */ + formattingProperties?: string[]; + } +} + +declare module powerbi.extensibility { + import DataViewObjectDescriptors = powerbi.data.DataViewObjectDescriptors; + + /** Defines the capabilities of an IVisual. */ + export interface VisualCapabilities { + /** Defines what roles the visual expects, and how those roles should be populated. This is useful for visual generation/editing. */ + dataRoles?: VisualDataRole[]; + + /** Defines the set of objects supported by this IVisual. */ + objects?: DataViewObjectDescriptors; + + /** Defines how roles that the visual understands map to the DataView. This is useful for query generation. */ + dataViewMappings?: DataViewMapping[]; + + /** Indicates whether cross-highlight is supported by the visual. This is useful for query generation. */ + supportsHighlight?: boolean; + + /** Indicates whether sorting is supported by the visual. This is useful for query generation */ + sorting?: VisualSortingCapabilities; + } +} + +declare module powerbi.extensibility { + + export function VisualPlugin(options: IVisualPluginOptions): ClassDecorator; + + export interface IVisualPluginOptions { + transform?: IVisualDataViewTransform; + } + + export interface IVisualConstructor { + __transform__?: IVisualDataViewTransform; + } + + export interface IVisualDataViewTransform { + (dataview: DataView[]): T; + } + + // These are the base interfaces. These should remain empty + // All visual versions should extend these for type compatability + + export interface IVisual { } + + export interface IVisualHost { } + + export interface VisualUpdateOptions { } + + export interface VisualConstructorOptions { } +} + +declare module powerbi.extensibility { + + export interface VisualVersionOverloads { + [name: string]: Function; + } + + export interface VisualVersionOverloadFactory { + (visual: powerbi.extensibility.IVisual): VisualVersionOverloads; + } + +} + +/** + * Change Log Version 1.1.0 + */ +declare module powerbi.extensibility.visual { + /** + * Represents a visualization displayed within an application (PowerBI dashboards, ad-hoc reporting, etc.). + * This interface does not make assumptions about the underlying JS/HTML constructs the visual uses to render itself. + */ + export interface IVisual extends extensibility.IVisual { + /** Notifies the IVisual of an update (data, viewmode, size change). */ + update(options: VisualUpdateOptions, viewModel: T): void; + + /** Notifies the visual that it is being destroyed, and to do any cleanup necessary (such as unsubscribing event handlers). */ + destroy?(): void; + + /** Gets the set of objects that the visual is currently displaying. */ + enumerateObjectInstances?(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration; + } + + export interface IVisualHost extends extensibility.IVisualHost { + createSelectionIdBuilder: () => visuals.ISelectionIdBuilder; + createSelectionManager: () => ISelectionManager; + /** An array of default colors to be used by the visual */ + colors: IColorInfo[]; + } + + export interface VisualUpdateOptions extends extensibility.VisualUpdateOptions { + viewport: IViewport; + dataViews: DataView[]; + type: VisualUpdateType; + viewMode?: ViewMode; + } + + export interface VisualConstructorOptions extends extensibility.VisualConstructorOptions { + element: HTMLElement; + host: IVisualHost; + } +} + +declare module powerbi.extensibility { + interface ISelectionManager { + select(selectionId: ISelectionId, multiSelect?: boolean): IPromise; + hasSelection(): boolean; + clear(): IPromise<{}>; + getSelectionIds(): ISelectionId[]; + } +} + +declare module powerbi.extensibility { + export interface Selector { } + + export interface ISelectionId { + equals(other: ISelectionId): boolean; + includes(other: ISelectionId, ignoreHighlight?: boolean): boolean; + getKey(): string; + getSelector(): Selector; + getSelectorsByColumn(): Selector; + hasIdentity(): boolean; + } + + export interface ISelectionIdBuilder { + withCategory(categoryColumn: DataViewCategoryColumn, index: number): this; + withSeries(seriesColumn: DataViewValueColumns, valueColumn: DataViewValueColumn | DataViewValueColumnGroup): this; + withMeasure(measureId: string): this; + createSelectionId(): ISelectionId; + } +} diff --git a/.api/v1.1.0/schema.capabilities.json b/.api/v1.1.0/schema.capabilities.json new file mode 100644 index 0000000..3ef1128 --- /dev/null +++ b/.api/v1.1.0/schema.capabilities.json @@ -0,0 +1,721 @@ +{ + "PBI_API_VERSION": "v1.1.0", + "type": "object", + "properties": { + "dataRoles": { + "type": "array", + "description": "Defines data roles for the visual", + "items": { + "$ref": "#/definitions/dataRole" + } + }, + "dataViewMappings": { + "type": "array", + "description": "Defines data mappings for the visual", + "items": { + "$ref": "#/definitions/dataViewMapping" + } + }, + "objects": { + "$ref": "#/definitions/objects" + }, + "sorting": { + "$ref": "#/definitions/sorting" + } + }, + "additionalProperties": false, + "definitions": { + "dataRole": { + "type": "object", + "description": "dataRole - Defines the name, displayName, and kind of a data role", + "properties": { + "name": { + "type": "string", + "description": "The internal name for this data role used for all references to this role" + }, + "displayName": { + "type": "string", + "description": "The name of this data role that is shown to the user" + }, + "kind": { + "type": "number", + "description": "The kind of data that can be bound do this role" + } + }, + "required": [ + "name", + "displayName", + "kind" + ], + "additionalProperties": false + }, + "dataViewMapping": { + "type": "object", + "description": "dataMapping - Defines how data is mapped to data roles", + "properties": { + "conditions": { + "type": "array", + "description": "List of conditions that must be met for this data mapping", + "items": { + "type": "object", + "description": "condition - Defines conditions for a data mapping (each key needs to be a valid data role)", + "patternProperties": { + "^\\w+$": { + "description": "Specifies the number of values that can be assigned to this data role in this mapping", + "$ref": "#/definitions/dataViewMapping.numberRange" + } + }, + "additionalProperties": false + } + }, + "single": { + "$ref": "#/definitions/dataViewMapping.single" + }, + "categorical": { + "$ref": "#/definitions/dataViewMapping.categorical" + }, + "table": { + "$ref": "#/definitions/dataViewMapping.table" + }, + "matrix": { + "$ref": "#/definitions/dataViewMapping.matrix" + } + }, + "oneOf": [ + { + "required": [ + "single" + ] + }, + { + "required": [ + "categorical" + ] + }, + { + "required": [ + "table" + ] + }, + { + "required": [ + "matrix" + ] + } + ], + "additionalProperties": false + }, + "dataViewMapping.single": { + "type": "object", + "description": "single - Defines a single data mapping", + "properties": { + "role": { + "type": "string", + "description": "The data role to bind to this mapping" + } + }, + "required": [ + "role" + ], + "additionalProperties": false + }, + "dataViewMapping.categorical": { + "type": "object", + "description": "categorical - Defines a categorical data mapping", + "properties": { + "categories": { + "type": "object", + "description": "Defines data roles to be used as categories", + "properties": { + "bind": { + "$ref": "#/definitions/dataViewMapping.bindTo" + }, + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + }, + "dataReductionAlgorithm": { + "$ref": "#/definitions/dataViewMapping.dataReductionAlgorithm" + } + }, + "oneOf": [ + { + "required": [ + "for" + ] + }, + { + "required": [ + "bind" + ] + } + ] + }, + "values": { + "type": "object", + "description": "Defines data roles to be used as values", + "properties": { + "bind": { + "$ref": "#/definitions/dataViewMapping.bindTo" + }, + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + }, + "select": { + "$ref": "#/definitions/dataViewMapping.select" + }, + "group": { + "type": "object", + "description": "Groups on a a specific data role", + "properties": { + "by": { + "description": "Specifies a data role to use for grouping", + "type": "string" + }, + "select": { + "$ref": "#/definitions/dataViewMapping.select" + }, + "dataReductionAlgorithm": { + "$ref": "#/definitions/dataViewMapping.dataReductionAlgorithm" + } + }, + "required": [ + "by", + "select" + ] + } + }, + "oneOf": [ + { + "required": [ + "for" + ] + }, + { + "required": [ + "bind" + ] + }, + { + "required": [ + "select" + ] + }, + { + "required": [ + "group" + ] + } + ] + } + }, + "additionalProperties": false + }, + "dataViewMapping.table": { + "type": "object", + "description": "table - Defines a table data mapping", + "properties": { + "rows": { + "type": "object", + "description": "Rows to use for the table", + "properties": { + "bind": { + "$ref": "#/definitions/dataViewMapping.bindTo" + }, + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + }, + "select": { + "$ref": "#/definitions/dataViewMapping.select" + }, + "dataReductionAlgorithm": { + "$ref": "#/definitions/dataViewMapping.dataReductionAlgorithm" + } + }, + "oneOf": [ + { + "required": [ + "for" + ] + }, + { + "required": [ + "bind" + ] + }, + { + "required": [ + "select" + ] + } + ] + }, + "rowCount": { + "type": "object", + "description": "Specifies a constraint on the number of data rows supported by the visual", + "properties": { + "preferred": { + "description": "Specifies a preferred range of values for the constraint", + "$ref": "#/definitions/dataViewMapping.numberRange" + }, + "supported": { + "description": "Specifies a supported range of values for the constraint. Defaults to preferred if not specified.", + "$ref": "#/definitions/dataViewMapping.numberRange" + } + } + } + }, + "requires": [ + "rows" + ] + }, + "dataViewMapping.matrix": { + "type": "object", + "description": "matrix - Defines a matrix data mapping", + "properties": { + "rows": { + "type": "object", + "description": "Defines the rows used for the matrix", + "properties": { + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + }, + "select": { + "$ref": "#/definitions/dataViewMapping.select" + }, + "dataReductionAlgorithm": { + "$ref": "#/definitions/dataViewMapping.dataReductionAlgorithm" + } + }, + "oneOf": [ + { + "required": [ + "for" + ] + }, + { + "required": [ + "select" + ] + } + ] + }, + "columns": { + "type": "object", + "description": "Defines the columns used for the matrix", + "properties": { + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + }, + "dataReductionAlgorithm": { + "$ref": "#/definitions/dataViewMapping.dataReductionAlgorithm" + } + }, + "required": [ + "for" + ] + }, + "values": { + "type": "object", + "description": "Defines the values used for the matrix", + "properties": { + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + }, + "select": { + "$ref": "#/definitions/dataViewMapping.select" + } + }, + "oneOf": [ + { + "required": [ + "for" + ] + }, + { + "required": [ + "select" + ] + } + ] + } + } + }, + "dataViewMapping.bindTo": { + "type": "object", + "description": "Binds this data mapping to a single value", + "properties": { + "to": { + "type": "string", + "description": "The name of a data role to bind to" + } + }, + "additionalProperties": false, + "required": [ + "to" + ] + }, + "dataViewMapping.numberRange": { + "type": "object", + "description": "A number range from min to max", + "properties": { + "min": { + "type": "number", + "description": "Minimum value supported" + }, + "max": { + "type": "number", + "description": "Maximum value supported" + } + } + }, + "dataViewMapping.select": { + "type": "array", + "description": "Defines a list of properties to bind", + "items": { + "type": "object", + "properties": { + "bind": { + "$ref": "#/definitions/dataViewMapping.bindTo" + }, + "for": { + "$ref": "#/definitions/dataViewMapping.forIn" + } + }, + "oneOf": [ + { + "required": [ + "for" + ] + }, + { + "required": [ + "bind" + ] + } + ] + } + }, + "dataViewMapping.dataReductionAlgorithm": { + "type": "object", + "description": "Describes how to reduce the amount of data exposed to the visual", + "properties": { + "top": { + "type": "object", + "description": "Reduce the data to the Top count items", + "properties": { + "count": { + "type": "number" + } + } + }, + "bottom": { + "type": "object", + "description": "Reduce the data to the Bottom count items", + "properties": { + "count": { + "type": "number" + } + } + }, + "sample": { + "type": "object", + "description": "Reduce the data using a simple Sample of count items", + "properties": { + "count": { + "type": "number" + } + } + }, + "window": { + "type": "object", + "description": "Allow the data to be loaded one window, containing count items, at a time", + "properties": { + "count": { + "type": "number" + } + } + } + }, + "additionalProperties": false, + "oneOf": [ + { + "required": [ + "top" + ] + }, + { + "required": [ + "bottom" + ] + }, + { + "required": [ + "sample" + ] + }, + { + "required": [ + "window" + ] + } + ] + }, + "dataViewMapping.forIn": { + "type": "object", + "description": "Binds this data mapping for all items in a collection", + "properties": { + "in": { + "type": "string", + "description": "The name of a data role to iterate over" + } + }, + "additionalProperties": false, + "required": [ + "in" + ] + }, + "objects": { + "type": "object", + "description": "A list of unique property groups", + "patternProperties": { + "^\\w+$": { + "type": "object", + "description": "Settings for a group of properties", + "properties": { + "displayName": { + "type": "string", + "description": "The name shown to the user to describe this group of properties" + }, + "properties": { + "type": "object", + "description": "A list of unique properties contained in this group", + "patternProperties": { + "^\\w+$": { + "$ref": "#/definitions/object.propertySettings" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "object.propertySettings": { + "type": "object", + "description": "Settings for a property", + "properties": { + "displayName": { + "type": "string", + "description": "The name shown to the user to describe this property" + }, + "description": { + "type": "string", + "description": "A description of this property shown to the user as a tooltip" + }, + "placeHolderText": { + "type": "string", + "description": "Text to display if the field is empty" + }, + "suppressFormatPainterCopy": { + "type": "boolean", + "description": "Indicates whether the Format Painter should ignore this property" + }, + "type": { + "type": "object", + "description": "Describes what type of property this is and how it should be displayed to the user", + "properties": { + "bool": { + "type": "boolean", + "description": "A boolean value that will be displayed to the user as a toggle switch" + }, + "enumeration": { + "type": "array", + "description": "A list of values that will be displayed as a drop down list", + "items": { + "type": "object", + "description": "Describes an item in the enumeration list", + "properties": { + "displayName": { + "type": "string", + "description": "The name shown to the user to describe this item" + }, + "value": { + "type": "string", + "description": "The internal value of this property when this item is selected" + } + } + } + }, + "fill": { + "type": "object", + "description": "A color value that will be displayed to the user as a color picker", + "properties": { + "solid": { + "type": "object", + "description": "A solid color value that will be displayed to the user as a color picker", + "properties": { + "color": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "nullable": { + "description": "Allows the user to select 'no fill' for the color", + "type": "boolean" + } + } + } + ] + } + } + } + } + }, + "formatting": { + "type": "object", + "description": "A numeric value that will be displayed to the user as a text input", + "properties": { + "labelDisplayUnits": { + "type": "boolean", + "description": "Displays a dropdown with common display units (Auto, None, Thousands, Millions, Billions, Trillions)" + }, + "alignment": { + "type": "boolean", + "description": "Displays a selector to allow the user to choose left, center, or right alignment" + }, + "fontSize": { + "type": "boolean", + "description": "Displays a slider that allows the user to choose a font size in points" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "required": [ + "labelDisplayUnits" + ] + }, + { + "required": [ + "alignment" + ] + }, + { + "required": [ + "fontSize" + ] + } + ] + }, + "numeric": { + "type": "boolean", + "description": "A numeric value that will be displayed to the user as a text input" + }, + "text": { + "type": "boolean", + "description": "A text value that will be displayed to the user as a text input" + } + }, + "additionalProperties": false, + "oneOf": [ + { + "required": [ + "bool" + ] + }, + { + "required": [ + "enumeration" + ] + }, + { + "required": [ + "fill" + ] + }, + { + "required": [ + "formatting" + ] + }, + { + "required": [ + "numeric" + ] + }, + { + "required": [ + "text" + ] + } + ] + } + }, + "additionalProperties": false + }, + "sorting": { + "type": "object", + "description": "Specifies the default sorting behavior for the visual", + "properties": { + "default": { + "type": "object", + "additionalProperties": false + }, + "custom": { + "type": "object", + "additionalProperties": false + }, + "implicit": { + "type": "object", + "description": "implicit sort", + "properties": { + "clauses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string" + }, + "direction": { + "type": "number", + "description": "Determines sort direction (1 = Ascending, 2 = Descending)", + "enum": [ + 1, + 2 + ] + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false, + "oneOf": [ + { + "required": [ + "default" + ] + }, + { + "required": [ + "custom" + ] + }, + { + "required": [ + "implicit" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/.api/v1.1.0/schema.pbiviz.json b/.api/v1.1.0/schema.pbiviz.json new file mode 100644 index 0000000..103ea20 --- /dev/null +++ b/.api/v1.1.0/schema.pbiviz.json @@ -0,0 +1,95 @@ +{ + "PBI_API_VERSION": "v1.1.0", + "type": "object", + "properties": { + "apiVersion": { + "type": "string", + "description": "Version of the IVisual API" + }, + "author": { + "type": "object", + "description": "Information about the author of the visual", + "properties": { + "name": { + "type": "string", + "description": "Name of the visual author. This is displayed to users." + }, + "email": { + "type": "string", + "description": "E-mail of the visual author. This is displayed to users for support." + } + } + }, + "assets": { + "type": "object", + "description": "Assets used by the visual", + "properties": { + "icon": { + "type": "string", + "description": "A 20x20 png icon used to represent the visual" + } + } + }, + "externalJS": { + "type": "array", + "description": "An array of relative paths to 3rd party javascript libraries to load", + "items": { + "type": "string" + } + }, + "style" : { + "type": "string", + "description": "Relative path to the stylesheet (less) for the visual" + }, + "capabilities": { + "type": "string", + "description": "Relative path to the visual capabilities json file" + }, + "visual": { + "type": "object", + "description": "Details about this visual", + "properties": { + "description": { + "type": "string", + "description": "What does this visual do?" + }, + "name": { + "type": "string", + "description": "Internal visual name" + }, + "displayName": { + "type": "string", + "description": "A friendly name" + }, + "externals": { + "type": "array", + "description": "External files (such as JavaScript) that you would like to include" + }, + "guid": { + "type": "string", + "description": "Unique identifier for the visual" + }, + "visualClassName": { + "type": "string", + "description": "Class of your IVisual" + }, + "icon": { + "type": "string", + "description": "Icon path" + }, + "version": { + "type": "string", + "description": "Visual version" + }, + "gitHubUrl": { + "type": "string", + "description": "Url to the github repository for this visual" + }, + "supportUrl": { + "type": "string", + "description": "Url to the support page for this visual" + } + } + } + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f1993c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.tmp +dist/ \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..6b4bb0b --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +typings +node_modules +.DS_Store +.tmp +dist \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d4e749d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Debugger", + "type": "chrome", + "request": "attach", + "port": 9222, + "sourceMaps": true, + "webRoot": "${cwd}/" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..49b6bdd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,31 @@ +{ + "editor.tabSize": 4, + "editor.insertSpaces": true, + "files.eol": "\n", + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/node_modules/**": true, + ".tmp": true + }, + "files.exclude": { + ".tmp": true + }, + "search.exclude": { + ".tmp": true, + "typings": true + }, + "json.schemas": [ + { + "fileMatch": [ + "/pbiviz.json" + ], + "url": "./.api/v1.1.0/schema.pbiviz.json" + }, + { + "fileMatch": [ + "/capabilities.json" + ], + "url": "./.api/v1.1.0/schema.capabilities.json" + } + ] +} diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0cff24cd1f33061b42b93aadf80d1db098a3a65f GIT binary patch literal 1162 zcmV;51aJXch8*L zH?rX#emxY`wUEs^Tq}c1*Wh9UuPTU}K$SzN`3!2r?>@A=>wn~`e;&B98c#3(hI(JPOUS_sso)Ji^fh4(1Q_FOvcR* z(9;|$Bh%KksOGiOjgiyoOjK;>U2;IT?pNg!>}P_#;#_k!I7hgZe{ZkE0quK*T0#rL z%k?=_eIhDla0&m%JR~w(6xM}BJ9j{D1@;~1J?CRA|F)>LW6@o+=)juL0)AUtK;nXv zi$tY};b(1XJUvYCEXc&o3ha0V7MRW{zXF!ex{xq)h9U*Ux~&Gv3&a%cKuFUXfldj! z{y%1Su>4FfEB;P42+xOp21NEwGwEd0SIx}ZR?YjSxgxhqR%4JVZ0_1y&;Db!@(|sS zwh9$Oc%CJ{`-x6@TZQ! zFu=-`6NrtyH#D+Aww<16ndzh^f<`Fn_$QXO4I4`e>@&?a0|=f48wQEOAAI$D3Or^G z+?s;YeH}h>9-%hTRMplP$of=d)11TH{J;$$muvtXAF>)D%@9EF?1D1ik{P#T!Gq_0 z=!NI^0-sibWa?FW!U(vM`K8iBLxFvs8eXml-h9oTg#aj~m`` z%c^KMJWG5swX|ern9|=P?q*o0umQg#;%(#Oh&3qT-3;1trEJMoumcESo3P1a6e(9V zI+6mJI1mGGP-~dGt~~9F$%jgh$uc`f#3+0?R0#n>nltYSdHFHE)b2_cAR+;?Vq1+7@gUs=?o)DO5Xn_K5Btkl8KR zHtM8nciLz(!DK(b=k c0sa+W0BcD1z9=jDdjJ3c07*qoM6N<$g48!6`~Uy| literal 0 HcmV?d00001 diff --git a/capabilities.json b/capabilities.json new file mode 100644 index 0000000..989f124 --- /dev/null +++ b/capabilities.json @@ -0,0 +1,106 @@ +{ + "dataRoles": [ + { + "name": "Category", + "kind": 0, + "displayName": "Category" + }, + { + "name": "X", + "kind": 1, + "displayName": "X values" + }, + { + "name": "Y", + "kind": 1, + "displayName": "Y values" + }, + { + "name": "I", + "kind": 1, + "displayName": "Intensity" + } + ], + "dataViewMappings": [ + { + "categorical": { + "categories": { + "for": { + "in": "Category" + } + }, + "values": { + "group": { + "by": "Series", + "select": [ + { + "bind": { + "to": "X" + } + }, + { + "bind": { + "to": "Y" + } + }, + { + "bind": { + "to": "I" + } + } + ] + } + } + } + } + ], + "objects": { + "settings": { + "displayName": "Heatmap Settings", + "properties": { + "backgroundUrl": { + "displayName": "Background Url", + "type": { + "text": true + } + }, + "toggle": { + "displayName": "Enable", + "type": { + "bool": true + } + }, + "radius": { + "displayName": "Radius", + "type": { + "numeric": true + } + }, + "blur": { + "displayName": "Blur", + "type": { + "numeric": true + } + }, + "maxValue": { + "displayName": "Max Intensity", + "type": { + "numeric": true + } + }, + "maxWidth": { + "displayName": "Canvas Width", + "type": { + "numeric": true + } + }, + "maxHeight": { + "displayName": "Canvas Height", + "type": { + "numeric": true + } + } + } + } + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..642f728 --- /dev/null +++ b/package.json @@ -0,0 +1,3 @@ +{ + "name": "visual" +} diff --git a/pbiviz.json b/pbiviz.json new file mode 100644 index 0000000..3c988f1 --- /dev/null +++ b/pbiviz.json @@ -0,0 +1,23 @@ +{ + "visual": { + "name": "heatmap", + "displayName": "Heatmap", + "guid": "PBI_CV_FCF70EF9_270E_4A52_913E_345CC4A8BFBA", + "visualClassName": "Visual", + "version": "2.0.0", + "description": "The Heatmap Visual enables users to draw a heatmap overlay from a X, Y coordinate set on to an existing image. The user specify the image, and provide a data set of X, Y coordinates and optionally an intensity for each data point. The radius and the bluriness of the heatmap bubbles can be customized as well as the max value for the intensity.", + "supportUrl": "http://powerbi.sjkp.dk/support", + "gitHubUrl": "https://github.com/sjkp/heatmap" + }, + "apiVersion": "1.1.0", + "author": { + "name": "SJKP", + "email": "powerbi@sjkp.dk" + }, + "assets": { + "icon": "assets/icon.png" + }, + "externalJS": [], + "style": "style/visual.less", + "capabilities": "capabilities.json" +} diff --git a/src/visual.ts b/src/visual.ts new file mode 100644 index 0000000..a7dd5e0 --- /dev/null +++ b/src/visual.ts @@ -0,0 +1,477 @@ +/* + * Power BI Visual CLI + * + * Copyright (c) Microsoft Corporation + * All rights reserved. + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the ""Software""), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +module powerbi.extensibility.visual { + export class SimpleHeatMap + { + /* + (c) 2014, Vladimir Agafonkin + simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas + https://github.com/mourner/simpleheat + Copyright (c) 2014, Vladimir Agafonkin + All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + private ctx: any; + private canvas: HTMLCanvasElement; + private circle: HTMLCanvasElement; + private width: number; + private height: number; + private maxValue: number; + private dataPoints: any; + private r: number; + private grad: Uint8ClampedArray; + + public defaultRadius = 5; + public defaultGradient = { + 0.4: 'blue', + 0.6: 'cyan', + 0.7: 'lime', + 0.8: 'yellow', + 1.0: 'red' + }; + + constructor(canvas) + { + // jshint newcap: false, validthis: true + //if (!(this instanceof SimpleHeatMap)) { return new SimpleHeatMap(canvas); } + + this.canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas; + + this.ctx = canvas.getContext('2d'); + this.width = canvas.width; + this.height = canvas.height; + + this.maxValue = 1; + this.dataPoints = []; + } + + public data(data) { + this.dataPoints = data; + return this; + } + + public max(max) { + this.maxValue = max; + return this; + } + + public add(point) { + this.dataPoints.push(point); + return this; + } + + public clear() { + this.dataPoints = []; + return this; + } + + public radius(r: number, blur?: number) { + blur = blur === undefined ? 15 : blur; + + // create a grayscale blurred circle image that we'll use for drawing points + var circle = this.circle = document.createElement('canvas'), + ctx = circle.getContext('2d'), + r2 = this.r = r + blur; + + circle.width = circle.height = r2 * 2; + + ctx.shadowOffsetX = ctx.shadowOffsetY = 200; + ctx.shadowBlur = blur; + ctx.shadowColor = 'black'; + + ctx.beginPath(); + ctx.arc(r2 - 200, r2 - 200, r, 0, Math.PI * 2, true); + ctx.closePath(); + ctx.fill(); + + return this; + } + + public gradient(grad) { + // create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + gradient = ctx.createLinearGradient(0, 0, 0, 256); + + canvas.width = 1; + canvas.height = 256; + + for (let i in grad) { + gradient.addColorStop(parseFloat(i), grad[i]); + } + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 1, 256); + + this.grad = ctx.getImageData(0, 0, 1, 256).data; + + return this; + } + + public draw(minOpacity?: number) { + if (!this.circle) { + this.radius(this.defaultRadius); + } + if (!this.grad) { + this.gradient(this.defaultGradient); + } + + var ctx = this.ctx; + + ctx.clearRect(0, 0, this.width, this.height); + + // draw a grayscale heatmap by putting a blurred circle at each data point + for (var i = 0, len = this.dataPoints.length, p; i < len; i++) { + p = this.dataPoints[i]; + + ctx.globalAlpha = Math.max(p[2] / this.maxValue, minOpacity === undefined ? 0.05 : minOpacity); + ctx.drawImage(this.circle, p[0] - this.r, p[1] - this.r); + } + + // colorize the heatmap, using opacity value of each pixel to get the right color from our gradient + var colored = ctx.getImageData(0, 0, this.width, this.height); + this._colorize(colored.data, this.grad); + ctx.putImageData(colored, 0, 0); + + return this; + } + private _colorize(pixels, gradient) { + for (var i = 3, len = pixels.length, j; i < len; i += 4) { + j = pixels[i] * 4; // get gradient color from opacity value + + if (j) { + pixels[i - 3] = gradient[j]; + pixels[i - 2] = gradient[j + 1]; + pixels[i - 1] = gradient[j + 2]; + } + } + } + } + + export interface HeatMapData { + x: number; + y: number; + i: number; + } + + export interface HeatMapDataModel { + dataArray: HeatMapData[]; + }; + + export class Visual implements IVisual { + + public currentViewport: IViewport; + + private element: HTMLElement; + private heatMap: SimpleHeatMap; + private dataView: DataView; + private backgroundUrl: string; + private defaultBackgroundUrl = ""; + private canvasWidth: number = 680; + private canvasHeight: number = 480; + private maxValue = 1; + + // Convert a DataView into a view model + public static converter(dataView: DataView): HeatMapDataModel { + console.log('converter', dataView); + var dataPoints: HeatMapData[] = []; + var xCol, yCol, iCol; + xCol = yCol = iCol = -1; + var index = 0; + var catDv: DataViewCategorical = dataView.categorical; + var values = catDv.values; + if (typeof (dataView.metadata.columns[0].roles) !== 'undefined') { + for (var i = 0; i < dataView.metadata.columns.length; i++) { + var colRole = Object.keys(dataView.metadata.columns[i].roles)[0]; + switch (colRole) { + case "X": + xCol = index; + break; + case "Y": + yCol = index; + break; + case "I": + iCol = index; + break; + case "Category": + index--; + break; + } + index++; + } + } else { + //For sandbox mode + xCol = 0; + yCol = 1; + iCol = 2; + if (typeof (values[xCol]) === 'undefined' || + typeof (values[yCol]) === 'undefined' || + (iCol !== -1 && typeof (values[iCol]) === 'undefined')) + { + return { + dataArray: dataPoints + }; + } + } + + for (var k = 0, kLen = values[0].values.length; k < kLen; k++) { + + dataPoints.push({ + x: values[xCol].values[k], + y: values[yCol].values[k], + i: iCol !== -1 ? values[iCol].values[k] : 1 + }); + } + + return { + dataArray: dataPoints + }; + } + + constructor(options: VisualConstructorOptions){ + this.intialize(options.element); + this.heatMap = new SimpleHeatMap(this.element); + } + + /* Called for data, size, formatting changes*/ + public update(options: VisualUpdateOptions) { + + this.dataView = options.dataViews[0]; + this.currentViewport = options.viewport; + this.updateBackgroundUrl(); + this.redrawCanvas(); + + } + + public redrawCanvas() { + //this.updateCanvasSize(); + this.updateInternal(false); + this.heatMap.max(Visual.getFieldNumber(this.dataView, 'settings', 'maxValue', this.maxValue)); + this.heatMap.radius(Visual.getFieldNumber(this.dataView, 'settings', 'radius', 5), Visual.getFieldNumber(this.dataView, 'settings', 'blur', 5)); + var data = Visual.converter(this.dataView); + this.heatMap.clear(); + this.heatMap.data(data.dataArray.map(s => { + return [s.x, s.y, s.i]; + })); + this.heatMap.draw(); + } + + /*About to remove your visual, do clean up here */ + public destroy() { + + } + + /* Called when the view port resizes */ + public onResizing(viewport: IViewport): void { + if (this.currentViewport.width !== viewport.width || this.currentViewport.height !== viewport.height) { + this.currentViewport = viewport; + this.updateInternal(false /* dataChanged */); + } + } + + + + //Make visual properties available in the property pane in Power BI + public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration { + var instances: VisualObjectInstance[] = []; + var dataView = this.dataView; + + switch (options.objectName) { + case 'settings': + var general: VisualObjectInstance = { + objectName: 'settings', + displayName: 'General', + selector: null, + properties: { + backgroundUrl: Visual.getFieldText(dataView, 'settings', 'backgroundUrl', ''), + radius: Visual.getFieldNumber(dataView, 'settings', 'radius',5), + blur: Visual.getFieldNumber(dataView, 'settings', 'blur',15), +// maxWidth: HeatMapChart.getFieldNumber(dataView, 'settings', 'maxWidth', this.canvasWidth), +// maxHeight: HeatMapChart.getFieldNumber(dataView, 'settings', 'maxHeight', this.canvasHeight), + maxValue: Visual.getFieldNumber(dataView, 'settings', 'maxValue', 1) + } + }; + instances.push(general); + break; + } + + return instances; + } + + public canResizeTo(viewport: IViewport): boolean { + return true; + } + + private getViewPort(): IViewport { + var currentViewport = this.currentViewport; + var mapViewport = { + width: currentViewport.width, + height: currentViewport.height + }; + + return mapViewport; + } + + private intialize(container: HTMLElement): void { + var canvas = document.createElement('canvas'); + canvas.className = "myClass"; + canvas.width = this.canvasWidth; + canvas.height = this.canvasHeight; + + container.appendChild(canvas); + + this.element = canvas; + this.updateBackgroundUrl(); + this.writeHelpOnCanvas(); + } + + private writeHelpOnCanvas(): void { + var canvas = this.element; + var context = canvas.getContext("2d"); + context.font = 'bold 10pt Calibri'; + function wrapText(context, text, x, y, maxWidth, lineHeight) { + var words = text.split(' '); + var line = ''; + + for (var n = 0; n < words.length; n++) { + var testLine = line + words[n] + ' '; + var metrics = context.measureText(testLine); + var testWidth = metrics.width; + if (testWidth > maxWidth && n > 0) { + context.fillText(line, x, y); + line = words[n] + ' '; + y += lineHeight; + } + else { + line = testLine; + } + } + context.fillText(line, x, y); + } + var border = 60; + var maxWidth = this.canvasWidth - border; + var lineHeight = 20; + var x = (this.canvasWidth - maxWidth) / 2; + var y = border; + wrapText(context, 'Select a background image, the width and height of the image should match the maximum x,y data points in the dataset.', x, y, maxWidth, lineHeight); + } + + private updateBackgroundUrl() { + var newBackgroundUrl = Visual.getFieldText(this.dataView, 'settings', 'backgroundUrl', this.defaultBackgroundUrl); + if (this.backgroundUrl !== newBackgroundUrl) { + var style = this.element.style; + + style.background = 'url("' + newBackgroundUrl + '")'; + style['background-size'] = '100% 100%'; + this.backgroundUrl = newBackgroundUrl; + var img = new Image(); + var self = this; + img.onload = function () { + self.updateCanvasSize(this.width, this.height); + //HeatMapChart.setFieldNumber(self.dataView, 'settings', 'maxWidth', this.width); + //HeatMapChart.setFieldNumber(self.dataView, 'settings', 'maxHeight', this.height); + self.redrawCanvas(); + }; + img.src = newBackgroundUrl; + } + } + + private updateCanvasSize(newWidth: number, newHeight: number) + { + debugger; + var updated = false; + //var newWidth = HeatMapChart.getFieldNumber(this.dataView, 'settings', 'maxWidth', this.canvasWidth); + if (this.canvasWidth !== newWidth) { + this.canvasWidth = newWidth; + (this.element).width = newWidth; + updated = true; + } + + //var newHeight = HeatMapChart.getFieldNumber(this.dataView, 'settings', 'maxHeight', this.canvasHeight); + if (this.canvasHeight !== newHeight ) { + this.canvasHeight = newHeight; + (this.element).height = newHeight; + updated = true; + } + if (updated) { + this.heatMap = new SimpleHeatMap(this.element); + } + } + + private updateInternal(redraw: boolean): void { + var mapViewport = this.getViewPort(); + var style = this.element.style; + style.height = mapViewport.height +'px'; + style.width = mapViewport.width+'px'; + } + + private static getFieldText(dataView: DataView, field: string, property: string = 'text', defaultValue: string = ''): string { + if (dataView) { + var objects = dataView.metadata.objects; + if (objects) { + var f = objects[field]; + if (f) { + var text = f[property]; + if (text) + return text; + } + } + } + return defaultValue; + } + + private static getFieldNumber(dataView: DataView, field: string, property: string = 'text', defaultValue: number = 100): number { + if (dataView) { + var objects = dataView.metadata.objects; + if (objects) { + var f = objects[field]; + if (f) { + var num = f[property]; + if (num) + return num; + } + } + } + return defaultValue; + } + } +} \ No newline at end of file diff --git a/style/visual.less b/style/visual.less new file mode 100644 index 0000000..9b1f1dd --- /dev/null +++ b/style/visual.less @@ -0,0 +1,9 @@ +p { + font-size: 20px; + font-weight: bold; + em { + background: yellow; + padding: 5px; + + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0ac3980 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "ES5", + "sourceMap": true, + "out": "./.tmp/build/visual.js" + }, + "files": [ + ".api/v1.1.0/PowerBI-visuals.d.ts", + "src/visual.ts" + ] +}