Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10b8a68e32 | |||
| 9e00cc85a8 | |||
| 047f763465 | |||
| fa33719902 | |||
| d60f0ba2b6 | |||
| aac875664d | |||
| ae6231f5f1 |
@@ -6,73 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
update_issue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get project data
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ORGANIZATION: specklesystems
|
||||
PROJECT_NUMBER: 9
|
||||
run: |
|
||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
query($org: String!, $number: Int!) {
|
||||
organization(login: $org){
|
||||
projectNext(number: $number) {
|
||||
id
|
||||
fields(first:20) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
settings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
|
||||
|
||||
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||
|
||||
echo "$PROJECT_ID"
|
||||
echo "$STATUS_FIELD_ID"
|
||||
|
||||
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
|
||||
echo "$DONE_ID"
|
||||
|
||||
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||
run: |
|
||||
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
mutation($project:ID!, $id:ID!) {
|
||||
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
|
||||
|
||||
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
||||
|
||||
- name: Update Status
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||
run: |
|
||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
|
||||
set_status: updateProjectNextItemField(
|
||||
input: {
|
||||
projectId: $project
|
||||
itemId: $id
|
||||
fieldId: $status
|
||||
value: $value
|
||||
}
|
||||
) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
|
||||
|
||||
uses: specklesystems/github-actions/.github/workflows/project-update-issue-status.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
issue-id: ${{ github.event.issue.node_id }}
|
||||
|
||||
@@ -6,45 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
track_issue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get project data
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ORGANIZATION: specklesystems
|
||||
PROJECT_NUMBER: 9
|
||||
run: |
|
||||
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
query($org: String!, $number: Int!) {
|
||||
organization(login: $org){
|
||||
projectNext(number: $number) {
|
||||
id
|
||||
fields(first:20) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
settings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
|
||||
|
||||
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
|
||||
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
|
||||
|
||||
- name: Add Issue to project
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
|
||||
ISSUE_ID: ${{ github.event.issue.node_id }}
|
||||
run: |
|
||||
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
|
||||
mutation($project:ID!, $id:ID!) {
|
||||
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
|
||||
projectNextItem {
|
||||
id
|
||||
}
|
||||
}
|
||||
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
|
||||
|
||||
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
|
||||
uses: specklesystems/github-actions/.github/workflows/project-add-issue.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
issue-id: ${{ github.event.issue.node_id }}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
+6
-4
@@ -31,15 +31,15 @@
|
||||
"in": "object"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"values": {
|
||||
"for": { "in": "objectData" },
|
||||
],
|
||||
"dataReductionAlgorithm": {
|
||||
"top": {
|
||||
"count": 30000
|
||||
}
|
||||
}
|
||||
},
|
||||
"values": {
|
||||
"for": { "in": "objectData" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,8 @@
|
||||
"supportsMultiVisualSelection": true,
|
||||
"suppressDefaultTitle": true,
|
||||
"supportsSynchronizingFilterState": true,
|
||||
"supportsLandingPage": true,
|
||||
"supportsEmptyDataView": true,
|
||||
"supportsKeyboardFocus": true,
|
||||
"tooltips": {
|
||||
"supportEnhancedTooltips": true
|
||||
|
||||
Generated
+2058
-275
File diff suppressed because it is too large
Load Diff
+5
-2
@@ -15,11 +15,14 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.6.0",
|
||||
"@babel/runtime-corejs2": "7.6.0",
|
||||
"@speckle/viewer": "2.7.1",
|
||||
"@speckle/viewer": "^2.11.4",
|
||||
"color-interpolate": "^1.0.5",
|
||||
"core-js": "3.2.1",
|
||||
"lodash": "^4.17.21",
|
||||
"powerbi-visuals-api": "~4.7.0",
|
||||
"powerbi-visuals-api": "~5.1.0",
|
||||
"powerbi-visuals-utils-dataviewutils": "2.4.1",
|
||||
"powerbi-visuals-utils-interactivityutils": "^5.7.1",
|
||||
"powerbi-visuals-utils-tooltiputils": "^2.5.2",
|
||||
"regenerator-runtime": "^0.13.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+2
-2
@@ -4,12 +4,12 @@
|
||||
"displayName": "Speckle PowerBI Viewer",
|
||||
"guid": "powerbiSpeckleVisualAA98F06515D847E8ACB33BAB487244E0",
|
||||
"visualClassName": "Visual",
|
||||
"version": "2.0.0-alpha3",
|
||||
"version": "2.0.0-alpha5",
|
||||
"description": "An interactive 3D viewer for Speckle Data",
|
||||
"supportUrl": "https://speckle.community",
|
||||
"gitHubUrl": "https://github.com/specklesystems/speckle-powerbi-visuals"
|
||||
},
|
||||
"apiVersion": "4.7.0",
|
||||
"apiVersion": "5.1.0",
|
||||
"author": { "name": "Speckle Systems", "email": "info@speckle.systems" },
|
||||
"assets": { "icon": "assets/logo.png" },
|
||||
"externalJS": null,
|
||||
|
||||
@@ -49,3 +49,13 @@ export function cleanupDataColumnName(name: string) {
|
||||
if (cleanName.startsWith("data.")) cleanName = cleanName.split("data.")[0]
|
||||
return cleanName
|
||||
}
|
||||
|
||||
export function projectToScreen(cam: any, loc: any) {
|
||||
cam.updateProjectionMatrix()
|
||||
var copy = loc.clone()
|
||||
copy.project(cam)
|
||||
return {
|
||||
x: (copy.x * 0.5 + 0.5) * window.innerWidth - 10,
|
||||
y: (copy.y * -0.5 + 0.5) * window.innerHeight
|
||||
}
|
||||
}
|
||||
|
||||
+299
-108
@@ -3,81 +3,90 @@
|
||||
import "core-js/stable"
|
||||
import "regenerator-runtime/runtime" /* <---- add this line */
|
||||
import "./../style/visual.less"
|
||||
|
||||
import powerbi from "powerbi-visuals-api"
|
||||
|
||||
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions
|
||||
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions
|
||||
import ITooltipService = powerbi.extensibility.ITooltipService
|
||||
import IVisual = powerbi.extensibility.visual.IVisual
|
||||
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions
|
||||
import VisualObjectInstance = powerbi.VisualObjectInstance
|
||||
import DataView = powerbi.DataView
|
||||
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject
|
||||
|
||||
import interpolate from "color-interpolate"
|
||||
|
||||
import { SpeckleVisualSettings } from "./settings"
|
||||
import { Viewer, DefaultViewerParams } from "@speckle/viewer"
|
||||
import * as _ from "lodash"
|
||||
import { VisualUpdateTypeToString, cleanupDataColumnName } from "./utils"
|
||||
import {
|
||||
Viewer,
|
||||
CanonicalView,
|
||||
ViewerEvent,
|
||||
PropertyInfo
|
||||
} from "@speckle/viewer"
|
||||
import _ from "lodash"
|
||||
import {
|
||||
VisualUpdateTypeToString,
|
||||
cleanupDataColumnName,
|
||||
projectToScreen
|
||||
} from "./utils"
|
||||
import { SettingsChangedType, Tracker } from "./mixpanel"
|
||||
|
||||
interface SpeckleTooltip {
|
||||
worldPos: {
|
||||
x: number
|
||||
y: number
|
||||
z: number
|
||||
}
|
||||
screenPos: {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
tooltip: any
|
||||
id: string
|
||||
}
|
||||
|
||||
export class Visual implements IVisual {
|
||||
private target: HTMLElement
|
||||
private settings: SpeckleVisualSettings
|
||||
private host: powerbi.extensibility.IVisualHost
|
||||
private selectionManager: powerbi.extensibility.ISelectionManager
|
||||
private selectionIdMap: Map<string, any>
|
||||
private tooltipService: ITooltipService
|
||||
|
||||
private selectionIdMap: Map<string, powerbi.extensibility.ISelectionId>
|
||||
private viewer: Viewer
|
||||
|
||||
private updateTask: Promise<void>
|
||||
private ac = new AbortController()
|
||||
private currentOrthoMode: boolean = false
|
||||
private currentDefaultView: string = "default"
|
||||
|
||||
private debounceWait = 500
|
||||
|
||||
private debounceUpdate = _.debounce(options => {
|
||||
this.initViewer().then(async _ => {
|
||||
if (this.updateTask) {
|
||||
this.ac.abort()
|
||||
console.log("Cancelling previous load job")
|
||||
await this.updateTask
|
||||
this.ac = new AbortController()
|
||||
}
|
||||
// Handle changes in the visual objects
|
||||
this.handleSettingsUpdate(options)
|
||||
console.log("Updating viewer with new data")
|
||||
// Handle the update in data passed to this visual
|
||||
this.updateTask = this.handleDataUpdate(options, this.ac.signal).then(
|
||||
() => (this.updateTask = undefined)
|
||||
)
|
||||
})
|
||||
}, this.debounceWait)
|
||||
private currentTooltip: SpeckleTooltip = null
|
||||
|
||||
constructor(options: VisualConstructorOptions) {
|
||||
Tracker.loaded()
|
||||
this.host = options.host
|
||||
|
||||
this.selectionIdMap = new Map<string, any>()
|
||||
this.selectionIdMap = new Map<string, powerbi.extensibility.ISelectionId>()
|
||||
//@ts-ignore
|
||||
this.selectionManager = this.host.createSelectionManager()
|
||||
|
||||
//@ts-ignore
|
||||
this.tooltipService = this.host.tooltipService as ITooltipService
|
||||
this.target = options.element
|
||||
}
|
||||
|
||||
public async initViewer() {
|
||||
if (this.viewer) {
|
||||
return
|
||||
}
|
||||
if (this.viewer) return
|
||||
|
||||
var container = this.target.appendChild(document.createElement("div"))
|
||||
container.style.backgroundColor = "transparent"
|
||||
container.style.height = "100%"
|
||||
container.style.width = "100%"
|
||||
container.style.position = "fixed"
|
||||
|
||||
const params = DefaultViewerParams
|
||||
|
||||
const viewer = new Viewer(container, params)
|
||||
var container = this.createContainerDiv()
|
||||
const viewer = new Viewer(container)
|
||||
await viewer.init()
|
||||
|
||||
// Setup any events here (progress, load-complete...)
|
||||
viewer.on(ViewerEvent.ObjectClicked, this.onObjectClicked)
|
||||
viewer.on(ViewerEvent.ObjectDoubleClicked, this.onObjectDoubleClicked)
|
||||
viewer.cameraHandler.controls.addEventListener(
|
||||
"update",
|
||||
this.throttleCameraUpdate
|
||||
)
|
||||
|
||||
this.viewer = viewer
|
||||
}
|
||||
@@ -87,6 +96,8 @@ export class Visual implements IVisual {
|
||||
options && options.dataViews && options.dataViews[0]
|
||||
)
|
||||
|
||||
this.HandleLandingPage(options)
|
||||
if (this.isLandingPageOn) return
|
||||
console.log(
|
||||
`Update was called with update type ${VisualUpdateTypeToString(
|
||||
options.type
|
||||
@@ -123,7 +134,7 @@ export class Visual implements IVisual {
|
||||
|
||||
// Handle change in default view
|
||||
if (this.currentDefaultView != this.settings.camera.defaultView) {
|
||||
this.viewer.interactions.rotateTo(this.settings.camera.defaultView)
|
||||
this.viewer.setView(this.settings.camera.defaultView as CanonicalView)
|
||||
this.currentDefaultView = this.settings.camera.defaultView
|
||||
Tracker.settingsChanged(SettingsChangedType.DefaultCamera)
|
||||
}
|
||||
@@ -144,6 +155,11 @@ export class Visual implements IVisual {
|
||||
: null
|
||||
if (!streamCategory || !objectIdCategory) {
|
||||
// If some of the fields are not filled in, unload everything
|
||||
//@ts-ignore
|
||||
this.host.displayWarningIcon(
|
||||
`Incomplete data input.`,
|
||||
`"Stream URL" and "Object ID" data inputs are mandatory`
|
||||
)
|
||||
console.warn(
|
||||
`Incomplete data input. "Stream URL" and "Object ID" data inputs are mandatory`
|
||||
)
|
||||
@@ -152,13 +168,9 @@ export class Visual implements IVisual {
|
||||
return
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
var selectionBuilder = this.host.createSelectionIdBuilder()
|
||||
|
||||
var objectUrls = streamCategory.map((stream, index) => {
|
||||
var url = `${stream}/objects/${objectIdCategory[index]}`
|
||||
return url
|
||||
})
|
||||
var objectUrls = streamCategory.map(
|
||||
(stream, index) => `${stream}/objects/${objectIdCategory[index]}`
|
||||
)
|
||||
|
||||
var objectsToUnload = []
|
||||
for (const key of this.selectionIdMap.keys()) {
|
||||
@@ -183,78 +195,77 @@ export class Visual implements IVisual {
|
||||
}
|
||||
|
||||
var index = 0
|
||||
var promises = []
|
||||
const batchSize = 25
|
||||
for (const url of objectUrls) {
|
||||
if (signal?.aborted) return
|
||||
if (!this.selectionIdMap.has(url)) {
|
||||
var selectionId = selectionBuilder.withCategory(
|
||||
categoricalView?.categories[1].values[index]
|
||||
)
|
||||
await this.viewer
|
||||
var promise = this.viewer
|
||||
.loadObject(url, null, false)
|
||||
.then(_ => {
|
||||
var url =
|
||||
categoricalView?.categories[0].values[index].toString() +
|
||||
"/objects/" +
|
||||
categoricalView?.categories[1].values[index].toString()
|
||||
this.selectionIdMap.set(url, selectionId)
|
||||
})
|
||||
.catch(e => {
|
||||
console.warn("Viewer Load error", url, e)
|
||||
.catch((e: Error) => {
|
||||
//@ts-ignore
|
||||
this.host.displayWarningIcon(
|
||||
"Load error",
|
||||
`One or more objects could not be loaded
|
||||
Please ensure that the stream you're trying to access is PUBLIC
|
||||
The Speckle PowerBI Viewer cannot handle private streams yet.`
|
||||
)
|
||||
console.warn("Viewer Load error:", url, e.name)
|
||||
})
|
||||
promises.push(promise)
|
||||
if (promises.length == batchSize) {
|
||||
await Promise.all(promises)
|
||||
promises = []
|
||||
}
|
||||
}
|
||||
// We create selection Ids for all objects, regardless if they're there already.
|
||||
//@ts-ignore
|
||||
var selectionBuilder = this.host.createSelectionIdBuilder()
|
||||
var selectionId = selectionBuilder
|
||||
.withCategory(categoricalView?.categories[1], index)
|
||||
.createSelectionId()
|
||||
this.selectionIdMap.set(url, selectionId)
|
||||
index++
|
||||
}
|
||||
|
||||
var colorList = this.settings.color.getColorList()
|
||||
// Once everything is loaded, run the filter
|
||||
var filter = null
|
||||
if (categoricalView?.values) {
|
||||
var name = categoricalView?.values[0].source.displayName
|
||||
var isNum =
|
||||
categoricalView?.values[0].source.type.numeric ||
|
||||
categoricalView?.values[0].source.type.integer
|
||||
var filterType = isNum ? "gradient" : "category"
|
||||
if (highlightedValues)
|
||||
filter = {
|
||||
filterBy: {
|
||||
id: highlightedValues
|
||||
.map((value, index) => (value ? objectIdCategory[index] : null))
|
||||
.filter(e => e != null)
|
||||
},
|
||||
ghostOthers: true,
|
||||
colorBy: {
|
||||
type: filterType,
|
||||
property: cleanupDataColumnName(name),
|
||||
gradientColors: isNum ? colorList : undefined,
|
||||
minValue: categoricalView?.values[0].minLocal,
|
||||
maxValue: categoricalView?.values[0].maxLocal
|
||||
}
|
||||
}
|
||||
else
|
||||
filter = {
|
||||
filterBy: {
|
||||
id: objectIdCategory
|
||||
},
|
||||
colorBy: {
|
||||
type: filterType,
|
||||
property: cleanupDataColumnName(name),
|
||||
gradientColors: isNum ? colorList : undefined,
|
||||
minValue: categoricalView?.values[0].minLocal,
|
||||
maxValue: categoricalView?.values[0].maxLocal
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(promises)
|
||||
|
||||
if (signal?.aborted) return
|
||||
Tracker.dataReload()
|
||||
console.log("Applying filter:", filter)
|
||||
return await this.viewer
|
||||
.applyFilter(filter)
|
||||
.catch(e => {
|
||||
console.warn("Filter failed to be applied. Filter will be reset", e)
|
||||
return this.viewer.applyFilter(null)
|
||||
})
|
||||
.then(_ => this.viewer.zoomExtents())
|
||||
|
||||
var colorList = this.settings.color.getColorList()
|
||||
if (categoricalView?.values) {
|
||||
var name = categoricalView?.values[0].source.displayName
|
||||
var objectIds = highlightedValues
|
||||
? highlightedValues
|
||||
.map((value, index) =>
|
||||
value ? objectIdCategory[index].toString() : null
|
||||
)
|
||||
.filter(e => e != null)
|
||||
: null
|
||||
if (objectIds) {
|
||||
await this.viewer.resetFilters()
|
||||
await this.viewer.isolateObjects(objectIds, null, true, true)
|
||||
} else {
|
||||
await this.viewer.resetFilters()
|
||||
}
|
||||
var prop = this.viewer
|
||||
.getObjectProperties(null, true)
|
||||
.find(item => item.key == cleanupDataColumnName(name))
|
||||
|
||||
if (prop.type == "number") {
|
||||
var groups = this.getCustomColorGroups(prop, colorList)
|
||||
//@ts-ignore
|
||||
await this.viewer.setUserObjectColors(groups)
|
||||
} else {
|
||||
await this.viewer.setColorFilter(prop).catch(async e => {
|
||||
console.warn("Filter failed to be applied. Filter will be reset", e)
|
||||
return await this.viewer.removeColorFilter()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await this.viewer.resetFilters()
|
||||
this.viewer.zoom()
|
||||
}
|
||||
}
|
||||
|
||||
private static parseSettings(dataView: DataView): SpeckleVisualSettings {
|
||||
@@ -274,4 +285,184 @@ export class Visual implements IVisual {
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
private debounceUpdate = _.debounce(options => {
|
||||
this.initViewer().then(async _ => {
|
||||
if (this.updateTask) {
|
||||
this.ac.abort()
|
||||
console.log("Cancelling previous load job")
|
||||
await this.updateTask
|
||||
this.ac = new AbortController()
|
||||
}
|
||||
// Handle changes in the visual objects
|
||||
this.handleSettingsUpdate(options)
|
||||
console.log("Updating viewer with new data")
|
||||
// Handle the update in data passed to this visual
|
||||
this.updateTask = this.handleDataUpdate(options, this.ac.signal).then(
|
||||
() => (this.updateTask = undefined)
|
||||
)
|
||||
})
|
||||
}, 500)
|
||||
|
||||
private throttleCameraUpdate = _.throttle(options => {
|
||||
if (!this.currentTooltip) return
|
||||
var newScreenLoc = projectToScreen(
|
||||
this.viewer.cameraHandler.camera,
|
||||
this.currentTooltip.worldPos
|
||||
)
|
||||
this.currentTooltip.tooltip.coordinates = [newScreenLoc.x, newScreenLoc.y]
|
||||
this.tooltipService.move(this.currentTooltip.tooltip)
|
||||
}, 1000.0 / 60.0)
|
||||
|
||||
private onObjectClicked = arg => {
|
||||
if (!arg) {
|
||||
this.tooltipService.hide({ immediately: true, isTouchEvent: false })
|
||||
this.currentTooltip = null
|
||||
this.viewer.resetSelection()
|
||||
this.selectionManager.clear()
|
||||
return
|
||||
}
|
||||
|
||||
var hit = arg.hits[0]
|
||||
this.viewer.selectObjects([hit.object.id])
|
||||
|
||||
this.showTooltip(hit)
|
||||
this.selectionManager.select(this.selectionIdMap.get(hit.guid), false)
|
||||
}
|
||||
|
||||
private onObjectDoubleClicked = arg => {
|
||||
if (!arg) return
|
||||
var hit = arg.hits[0]
|
||||
var selectionId = this.selectionIdMap.get(hit.guid)
|
||||
const screenLoc = projectToScreen(
|
||||
this.viewer.cameraHandler.camera,
|
||||
hit.point
|
||||
)
|
||||
this.selectionManager.showContextMenu(selectionId, screenLoc)
|
||||
}
|
||||
|
||||
private createContainerDiv() {
|
||||
var container = this.target.appendChild(document.createElement("div"))
|
||||
container.style.backgroundColor = "transparent"
|
||||
container.style.height = "100%"
|
||||
container.style.width = "100%"
|
||||
container.style.position = "fixed"
|
||||
return container
|
||||
}
|
||||
|
||||
private showTooltip(hit: any) {
|
||||
var selectionId = this.selectionIdMap.get(hit.guid)
|
||||
const screenLoc = projectToScreen(
|
||||
this.viewer.cameraHandler.camera,
|
||||
hit.point
|
||||
)
|
||||
var dataItems = Object.keys(hit.object)
|
||||
.filter(key => !key.startsWith("__"))
|
||||
.map(key => {
|
||||
return {
|
||||
displayName: key,
|
||||
value: hit.object[key]
|
||||
}
|
||||
})
|
||||
|
||||
const tooltipData = {
|
||||
coordinates: [screenLoc.x, screenLoc.y],
|
||||
dataItems: dataItems,
|
||||
identities: [selectionId],
|
||||
isTouchEvent: false
|
||||
}
|
||||
|
||||
this.currentTooltip = {
|
||||
id: hit.object.id,
|
||||
worldPos: hit.point,
|
||||
screenPos: screenLoc,
|
||||
tooltip: tooltipData
|
||||
}
|
||||
this.tooltipService.show(tooltipData)
|
||||
}
|
||||
|
||||
private isLandingPageOn = false
|
||||
private LandingPageRemoved = false
|
||||
|
||||
private LandingPage: Element = null
|
||||
|
||||
private HandleLandingPage(options: VisualUpdateOptions) {
|
||||
console.log("handle landing page")
|
||||
if (
|
||||
!options.dataViews ||
|
||||
!options.dataViews[0]?.metadata?.columns?.length
|
||||
) {
|
||||
if (!this.isLandingPageOn) {
|
||||
this.isLandingPageOn = true
|
||||
const SampleLandingPage: Element = this.createSampleLandingPage() //create a landing page
|
||||
this.LandingPage = SampleLandingPage
|
||||
}
|
||||
} else {
|
||||
if (this.isLandingPageOn && !this.LandingPageRemoved) {
|
||||
this.LandingPageRemoved = true
|
||||
this.target.removeChild(this.LandingPage)
|
||||
this.isLandingPageOn = false
|
||||
}
|
||||
}
|
||||
}
|
||||
createSampleLandingPage(): Element {
|
||||
var container = this.target.appendChild(document.createElement("div"))
|
||||
container.classList.add("speckle-landing")
|
||||
|
||||
var img = document.createElement("div")
|
||||
img.classList.add("speckle-logo")
|
||||
container.appendChild(img)
|
||||
|
||||
var subtext = document.createElement("p")
|
||||
subtext.classList.add("heading")
|
||||
subtext.textContent = "PowerBI 3D Viewer"
|
||||
container.appendChild(subtext)
|
||||
|
||||
var tipContainer = document.createElement("div")
|
||||
tipContainer.classList.add("tip-container")
|
||||
|
||||
var tip = document.createElement("p")
|
||||
tip.textContent = "Getting started 💡"
|
||||
tip.classList.add("tip")
|
||||
tipContainer.appendChild(tip)
|
||||
|
||||
var instructions = document.createElement("p")
|
||||
instructions.classList.add("instructions")
|
||||
instructions.textContent =
|
||||
"Please connect the Stream ID and Object ID fields."
|
||||
tipContainer.appendChild(instructions)
|
||||
|
||||
var instructions2 = document.createElement("p")
|
||||
instructions2.classList.add("instructions")
|
||||
instructions2.textContent =
|
||||
"Optionally, connect the 'Object Data' field to color the objects by a value"
|
||||
tipContainer.appendChild(instructions2)
|
||||
|
||||
var instructions2 = document.createElement("p")
|
||||
instructions2.classList.add("instructions")
|
||||
instructions2.classList.add("docs")
|
||||
instructions2.innerHTML =
|
||||
"For more info, check our docs page <b>https://speckle.guide</b>"
|
||||
tipContainer.appendChild(instructions2)
|
||||
|
||||
container.appendChild(tipContainer)
|
||||
return container
|
||||
}
|
||||
|
||||
private getCustomColorGroups(prop: PropertyInfo, customColors: string[]) {
|
||||
var groups: [{ value: number; id?: string; ids?: string[] }] =
|
||||
//@ts-ignore
|
||||
prop.valueGroups
|
||||
if (!groups) return null
|
||||
var colorGrad = interpolate(customColors)
|
||||
return groups.map(group => {
|
||||
//@ts-ignore
|
||||
var color = colorGrad((group.value - prop.min) / (prop.max - prop.min))
|
||||
var objectIds = group.ids ?? [group.id]
|
||||
return {
|
||||
objectIds,
|
||||
color
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+52
-8
@@ -1,9 +1,53 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap");
|
||||
|
||||
p {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
em {
|
||||
background: yellow;
|
||||
padding: 5px;
|
||||
|
||||
}
|
||||
}
|
||||
font-size: 15px;
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
}
|
||||
|
||||
.speckle-landing {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.speckle-logo {
|
||||
background-image: url("../assets/logo-blue-2.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
min-width: 200px;
|
||||
min-height: 60px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
color: grey;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tip-container {
|
||||
background-color: gainsboro;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
+14
-18
@@ -1,19 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"outDir": "./.tmp/build/",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"lib": [
|
||||
"es2015",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"./src/visual.ts"
|
||||
]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true,
|
||||
"outDir": "./.tmp/build/",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"lib": ["es2015", "dom"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"files": ["./src/visual.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user