The ultimate Fabric, Power BI, SQL, and AI community-led learning event. Save €200 with code FABCOMM.
Get registeredCompete to become Power BI Data Viz World Champion! First round ends August 18th. Get started.
Hi,
I am working on a Power BI Proof of Concept project that involves ArcGIS Map visuals. My organization has custom ArcGIS maps that need to be integrated with the Power BI report. The challenge here is that the project needs to be deployed in an environment where ArcGIS online access (login & connect to the Argis portal through the Argis map visualization) is not working.
The idea is to get the HTML code of the map and use it directly in the report to avoid access issues. I tried the method below.
I hosted the HTML code of the map on GitHub and established a GitHub page. Following this, I created a Power BI table, with the URL as one of the column values. Subsequently, I crafted a measure based on the table and integrated the measure's value into an HTML iframe in another measure, as demonstrated below
IFrame =
"<!DOCTYPE html>
<html>
<body>
<iframe src='"& [murl] &"' style='position: fixed; width:100%; height:100%'></iframe>
</body>
</html>"
This Iframe measure then used as "Value" in the "Html Content" visual.
Based on the above steps I am able to display the interactive Custom Argis Map.
The current requirement is to incorporate the HTML code of a visual directly within Power BI, facilitating data integration with other datasets using DAX. I attempted to embed the HTML code into a measure and used it within the HTML Content visual, but this resulted in a blank page.
Below is the URL of the Argis sample map : https://developers.arcgis.com/javascript/latest/sample-code/layers-scenelayerview-query-stats/live/
Below is the HTML code behind this map.
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>SceneLayerView - query statistics by geometry | Sample | ArcGIS Maps SDK for JavaScript 4.28</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
.esri-ui-top-right {
max-height: 100%;
}
#queryDiv,
#resultDiv {
min-width: 250px;
font-size: 14px;
padding: 10px;
display: none;
overflow-y: auto;
overflow-x: hidden;
}
.geometry-options {
display: flex;
flex-direction: row;
}
.geometry-button {
flex: 1;
border-style: solid;
border-width: 1px;
border-image: none;
}
.geometry-button-selected {
background: #4c4c4c;
color: #fff;
}
#bufferNum {
width: 90%;
margin: 2.5em auto 0;
}
.count {
white-space: nowrap;
font-size: 14px;
font-weight: bold;
display: inline-block;
}
</style>
<!-- Load the Chart.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js"></script>
<link rel="stylesheet" href="https://js.arcgis.com/4.28/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.28/"></script>
<script>
require([
"esri/WebScene",
"esri/views/SceneView",
"esri/layers/GraphicsLayer",
"esri/widgets/Sketch/SketchViewModel",
"esri/widgets/Slider",
"esri/geometry/geometryEngine",
"esri/Graphic",
"esri/core/promiseUtils"
], (WebScene, SceneView, GraphicsLayer, SketchViewModel, Slider, geometryEngine, Graphic, promiseUtils) => {
// Load webscene and display it in a SceneView
const webscene = new WebScene({
portalItem: {
id: "fb5948b2bb76439792786000b942e5b7"
}
});
// create the SceneView
const view = new SceneView({
container: "viewDiv",
map: webscene
});
window.view = view;
// add a GraphicsLayer for the sketches and the buffer
const sketchLayer = new GraphicsLayer();
const bufferLayer = new GraphicsLayer();
view.map.addMany([bufferLayer, sketchLayer]);
let sceneLayer = null;
let sceneLayerView = null;
let bufferSize = 0;
// Assign scene layer once webscene is loaded and initialize UI
webscene.load().then(() => {
sceneLayer = webscene.layers.find((layer) => {
return layer.title === "Helsinki textured buildings";
});
sceneLayer.outFields = ["buildingMaterial", "yearCompleted"];
view.whenLayerView(sceneLayer).then((layerView) => {
sceneLayerView = layerView;
queryDiv.style.display = "block";
});
});
view.ui.add([queryDiv], "bottom-left");
view.ui.add([resultDiv], "top-right");
// use SketchViewModel to draw polygons that are used as a query
let sketchGeometry = null;
const sketchViewModel = new SketchViewModel({
layer: sketchLayer,
defaultUpdateOptions: {
tool: "reshape",
toggleToolOnClick: false
},
view: view,
defaultCreateOptions: { hasZ: false }
});
sketchViewModel.on("create", (event) => {
if (event.state === "complete") {
sketchGeometry = event.graphic.geometry;
runQuery();
}
});
sketchViewModel.on("update", (event) => {
if (event.state === "complete") {
sketchGeometry = event.graphics[0].geometry;
runQuery();
}
});
// draw geometry buttons - use the selected geometry to sktech
const pointBtn = document.getElementById("point-geometry-button");
const lineBtn = document.getElementById("line-geometry-button");
const polygonBtn = document.getElementById("polygon-geometry-button");
pointBtn.addEventListener("click", geometryButtonsClickHandler);
lineBtn.addEventListener("click", geometryButtonsClickHandler);
polygonBtn.addEventListener("click", geometryButtonsClickHandler);
function geometryButtonsClickHandler(event) {
const geometryType = event.target.value;
clearGeometry();
sketchViewModel.create(geometryType);
}
const bufferNumSlider = new Slider({
container: "bufferNum",
min: 0,
max: 500,
steps: 1,
visibleElements: {
labels: true
},
precision: 0,
labelFormatFunction: (value, type) => {
return `${value.toString()}m`;
},
values: [0]
});
// get user entered values for buffer
bufferNumSlider.on(["thumb-change", "thumb-drag"], bufferVariablesChanged);
function bufferVariablesChanged(event) {
bufferSize = event.value;
runQuery();
}
// Clear the geometry and set the default renderer
const clearGeometryBtn = document.getElementById("clearGeometry");
clearGeometryBtn.addEventListener("click", clearGeometry);
// Clear the geometry and set the default renderer
function clearGeometry() {
sketchGeometry = null;
sketchViewModel.cancel();
sketchLayer.removeAll();
bufferLayer.removeAll();
clearHighlighting();
clearCharts();
resultDiv.style.display = "none";
}
// set the geometry query on the visible SceneLayerView
const debouncedRunQuery = promiseUtils.debounce(() => {
if (!sketchGeometry) {
return;
}
resultDiv.style.display = "block";
updateBufferGraphic(bufferSize);
return promiseUtils.eachAlways([queryStatistics(), updateSceneLayer()]);
});
function runQuery() {
debouncedRunQuery().catch((error) => {
if (error.name === "AbortError") {
return;
}
console.error(error);
});
}
// Set the renderer with objectIds
let highlightHandle = null;
function clearHighlighting() {
if (highlightHandle) {
highlightHandle.remove();
highlightHandle = null;
}
}
function highlightBuildings(objectIds) {
// Remove any previous highlighting
clearHighlighting();
const objectIdField = sceneLayer.objectIdField;
document.getElementById("count").innerHTML = objectIds.length;
highlightHandle = sceneLayerView.highlight(objectIds);
}
// update the graphic with buffer
function updateBufferGraphic(buffer) {
// add a polygon graphic for the buffer
if (buffer > 0) {
const bufferGeometry = geometryEngine.geodesicBuffer(sketchGeometry, buffer, "meters");
if (bufferLayer.graphics.length === 0) {
bufferLayer.add(
new Graphic({
geometry: bufferGeometry,
symbol: sketchViewModel.polygonSymbol
})
);
} else {
bufferLayer.graphics.getItemAt(0).geometry = bufferGeometry;
}
} else {
bufferLayer.removeAll();
}
}
function updateSceneLayer() {
const query = sceneLayerView.createQuery();
query.geometry = sketchGeometry;
query.distance = bufferSize;
return sceneLayerView.queryObjectIds(query).then(highlightBuildings);
}
let yearChart = null;
let materialChart = null;
function queryStatistics() {
const statDefinitions = [
{
onStatisticField: "CASE WHEN buildingMaterial = 'concrete or lightweight concrete' THEN 1 ELSE 0 END",
outStatisticFieldName: "material_concrete",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN buildingMaterial = 'brick' THEN 1 ELSE 0 END",
outStatisticFieldName: "material_brick",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN buildingMaterial = 'wood' THEN 1 ELSE 0 END",
outStatisticFieldName: "material_wood",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN buildingMaterial = 'steel' THEN 1 ELSE 0 END",
outStatisticFieldName: "material_steel",
statisticType: "sum"
},
{
onStatisticField:
"CASE WHEN buildingMaterial IN ('concrete or lightweight concrete', 'brick', 'wood', 'steel') THEN 0 ELSE 1 END",
outStatisticFieldName: "material_other",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN (yearCompleted >= '1850' AND yearCompleted <= '1899') THEN 1 ELSE 0 END",
outStatisticFieldName: "year_1850",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN (yearCompleted >= '1900' AND yearCompleted <= '1924') THEN 1 ELSE 0 END",
outStatisticFieldName: "year_1900",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN (yearCompleted >= '1925' AND yearCompleted <= '1949') THEN 1 ELSE 0 END",
outStatisticFieldName: "year_1925",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN (yearCompleted >= '1950' AND yearCompleted <= '1974') THEN 1 ELSE 0 END",
outStatisticFieldName: "year_1950",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN (yearCompleted >= '1975' AND yearCompleted <= '1999') THEN 1 ELSE 0 END",
outStatisticFieldName: "year_1975",
statisticType: "sum"
},
{
onStatisticField: "CASE WHEN (yearCompleted >= '2000' AND yearCompleted <= '2015') THEN 1 ELSE 0 END",
outStatisticFieldName: "year_2000",
statisticType: "sum"
}
];
const query = sceneLayerView.createQuery();
query.geometry = sketchGeometry;
query.distance = bufferSize;
query.outStatistics = statDefinitions;
return sceneLayerView.queryFeatures(query).then((result) => {
const allStats = result.features[0].attributes;
updateChart(materialChart, [
allStats.material_concrete,
allStats.material_brick,
allStats.material_wood,
allStats.material_steel,
allStats.material_other
]);
updateChart(yearChart, [
allStats.year_1850,
allStats.year_1900,
allStats.year_1925,
allStats.year_1950,
allStats.year_1975,
allStats.year_2000
]);
}, console.error);
}
// Updates the given chart with new data
function updateChart(chart, dataValues) {
chart.data.datasets[0].data = dataValues;
chart.update();
}
function createYearChart() {
const yearCanvas = document.getElementById("year-chart");
yearChart = new Chart(yearCanvas.getContext("2d"), {
type: "horizontalBar",
data: {
labels: ["1850-1899", "1900-1924", "1925-1949", "1950-1974", "1975-1999", "2000-2015"],
datasets: [
{
label: "Build year",
backgroundColor: "#149dcf",
stack: "Stack 0",
data: [0, 0, 0, 0, 0, 0]
}
]
},
options: {
responsive: false,
legend: {
display: false
},
title: {
display: true,
text: "Build year"
},
scales: {
xAxes: [
{
stacked: true,
ticks: {
beginAtZero: true,
precision: 0
}
}
],
yAxes: [
{
stacked: true
}
]
}
}
});
}
function createMaterialChart() {
const materialCanvas = document.getElementById("material-chart");
materialChart = new Chart(materialCanvas.getContext("2d"), {
type: "doughnut",
data: {
labels: ["Concrete", "Brick", "Wood", "Steel", "Other"],
datasets: [
{
backgroundColor: ["#FD7F6F", "#7EB0D5", "#B2E061", "#BD7EBE", "#FFB55A"],
borderWidth: 0,
data: [0, 0, 0, 0, 0]
}
]
},
options: {
responsive: false,
cutoutPercentage: 35,
legend: {
position: "bottom"
},
title: {
display: true,
text: "Building Material"
}
}
});
}
function clearCharts() {
updateChart(materialChart, [0, 0, 0, 0, 0]);
updateChart(yearChart, [0, 0, 0, 0, 0, 0]);
document.getElementById("count").innerHTML = 0;
}
createYearChart();
createMaterialChart();
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="queryDiv" class="esri-widget">
<b>Query by geometry</b><br />
<br />Draw a geometry to query by:
<div class="geometry-options">
<button
class="esri-widget--button esri-icon-map-pin geometry-button"
id="point-geometry-button"
value="point"
title="Query by point"
></button>
<button
class="esri-widget--button esri-icon-polyline geometry-button"
id="line-geometry-button"
value="polyline"
title="Query by line"
></button>
<button
class="esri-widget--button esri-icon-polygon geometry-button"
id="polygon-geometry-button"
value="polygon"
title="Query by polygon"
></button>
</div>
<br />
<div class="tooltip">
<label for="bufferNum">Set a geometry buffer size:</label>
<div id="bufferNum"></div>
</div>
<br />
<button class="esri-button" id="clearGeometry" type="button">Clear</button>
</div>
<div id="resultDiv" class="esri-widget">
<div class="count">
Selected Buildings:
<div class="count" id="count">0</div>
</div>
<div class="charts">
<div>
<canvas id="year-chart" height="250" width="260" />
</div>
<div>
<canvas id="material-chart" width="250" height="300" />
</div>
</div>
</div>
</body>
</html>
Is there anyway i can use this HTML code directly and display an interactive MAP in power bi instead of hosting it in github & use Iframe. Is there any visual other than "HTML content" can be used to display rich HTML (with Java script, CSS) ?
Any help much appreciated.
I tried Most of HTML visual apps in the Appstore. None of them seems to be supporting this.
Hi @BmBIDev ,
Usually it's all about customizing visual objects in powerbi. If you use HTML you may not be able to fully apply your needs. You can try Business Apps – Microsoft AppSource
Hope it helps!
Best regards,
Community Support Team_ Scott Chang
If this post helps then please consider Accept it as the solution to help the other members find it more quickly.