diff --git a/demos/dashboards-xray/demo.css b/demos/dashboards-xray/demo.css
new file mode 100644
index 0000000..23cc8f9
--- /dev/null
+++ b/demos/dashboards-xray/demo.css
@@ -0,0 +1,38 @@
+@import url("https://code.highcharts.com/dashboards/css/datagrid.css");
+@import url("https://code.highcharts.com/css/highcharts.css");
+@import url("https://code.highcharts.com/dashboards/css/dashboards.css");
+
+body {
+ font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, sans-serif;
+}
+
+.row {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.cell {
+ flex: 1;
+ min-width: 20px;
+}
+
+.cell > .highcharts-dashboards-component {
+ position: relative;
+ margin: 10px;
+ background-clip: border-box;
+}
+
+.highcharts-dashboards-component-title {
+ padding: 10px;
+ margin: 0;
+ background-color: var(--highcharts-neutral-color-5);
+ color: var(--highcharts-neutral-color-100);
+ border: solid 1px var(--highcharts-neutral-color-20);
+ border-bottom: none;
+}
+
+@media screen and (max-width: 1000px) {
+ .row {
+ flex-direction: column;
+ }
+}
diff --git a/demos/dashboards-xray/demo.html b/demos/dashboards-xray/demo.html
new file mode 100644
index 0000000..58fa1c7
--- /dev/null
+++ b/demos/dashboards-xray/demo.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+ Highcharts Dashboards DataGrid + Morningstar XRay Connector
+
+
+ Highcharts Dashboards DataGrid + Morningstar XRay Connector
+
+ Add your Postman environment file from Morningstar to start the demo:
+
+
+ Loading data…
+
+
+
+
diff --git a/demos/dashboards-xray/demo.js b/demos/dashboards-xray/demo.js
new file mode 100644
index 0000000..06dd06d
--- /dev/null
+++ b/demos/dashboards-xray/demo.js
@@ -0,0 +1,216 @@
+const globalStockSectorMap = {
+ 99: 'Not Classified',
+ 101: 'Basic Materials',
+ 102: 'Consumer Staples',
+ 103: 'Financial Services',
+ 104: 'Real Estate',
+ 205: 'Consumer Defensive',
+ 206: 'Healthcare',
+ 207: 'Utilities',
+ 308: 'Communication Services',
+ 309: 'Energy',
+ 310: 'Industrials',
+ 311: 'Technology'
+};
+
+const assetAllocationMap = {
+ 1: 'Stock',
+ 2: 'Bond',
+ 3: 'Cash',
+ 4: 'Other',
+ 99: 'Not Classified'
+};
+
+const regionalExposureMap = {
+ 1: 'United States',
+ 2: 'Canada',
+ 3: 'Latin America',
+ 4: 'United Kingdom',
+ 5: 'Eurozone',
+ 6: 'Europe - ex Euro',
+ 7: 'Europe - Emerging',
+ 8: 'Africa',
+ 9: 'Middle East',
+ 10: 'Japan',
+ 11: 'Australasia',
+ 12: 'Asia - Developed',
+ 13: 'Asia - Emerging',
+ 14: 'Emerging Market',
+ 15: 'Developed Country',
+ 16: 'Not Classified',
+ 99: 'Other'
+}
+
+async function displaySecurityDetails (postmanJSON) {
+ const board = Dashboards.board('container', {
+ dataPool: {
+ connectors: [{
+ id: 'xray',
+ type: 'MorningstarXRay',
+ options: {
+ postman: {
+ environmentJSON: postmanJSON
+ },
+ currencyId: 'GBP',
+ dataPoints: {
+ type: 'portfolio',
+ dataPoints: [
+ 'AssetAllocationMorningstarEUR3',
+ 'GlobalStockSector',
+ 'RegionalExposure'
+ ]
+ },
+ holdings: [
+ {
+ id: 'F0GBR052QA',
+ idType: 'MSID',
+ type: 'FO',
+ weight: '100',
+ name: 'BlackRock Income and Growth Ord',
+ holdingType: 'weight'
+ }
+ ]
+ }
+ }]
+ },
+ components: [
+ {
+ renderTo: 'dashboard-col-0',
+ connector: {
+ id: 'xray'
+ },
+ type: 'DataGrid',
+ title: 'Global Stock Sector',
+ dataGridOptions: {
+ header: [{
+ format: 'Net',
+ columnId: 'XRay_GlobalStockSector_N_Categories'
+ }, {
+ format: 'Values',
+ columnId: 'XRay_GlobalStockSector_N_Values'
+ }],
+ columns: [{
+ id: 'XRay_GlobalStockSector_N_Categories',
+ cells: {
+ formatter: function () {
+ return this.value !== void 0 ?
+ globalStockSectorMap[this.value] : '';
+ }
+ }
+ }]
+ }
+ }, {
+ renderTo: 'dashboard-col-1',
+ connector: {
+ id: 'xray'
+ },
+ type: 'DataGrid',
+ title: 'Morningstar EUR3',
+ dataGridOptions: {
+ header: [{
+ format: 'Long',
+ columnId: 'XRay_MorningstarEUR3_L_Categories'
+ }, {
+ format: 'Values',
+ columnId: 'XRay_MorningstarEUR3_L_Values'
+ }, {
+ format: 'Net',
+ columnId: 'XRay_MorningstarEUR3_N_Categories'
+ }, {
+ format: 'Values',
+ columnId: 'XRay_MorningstarEUR3_N_Values'
+ }, {
+ format: 'Short',
+ columnId: 'XRay_MorningstarEUR3_S_Categories'
+ }, {
+ format: 'Values',
+ columnId: 'XRay_MorningstarEUR3_S_Values'
+ }],
+ columns: [{
+ id: 'XRay_MorningstarEUR3_L_Categories',
+ cells: {
+ formatter: function () {
+ return this.value !== void 0 ?
+ assetAllocationMap[this.value] : '';
+ }
+ }
+ }, {
+ id: 'XRay_MorningstarEUR3_N_Categories',
+ cells: {
+ formatter: function () {
+ return this.value !== void 0 ?
+ assetAllocationMap[this.value] : '';
+ }
+ }
+ }, {
+ id: 'XRay_MorningstarEUR3_S_Categories',
+ cells: {
+ formatter: function () {
+ return this.value !== void 0 ?
+ assetAllocationMap[this.value] : '';
+ }
+ }
+ }]
+ }
+ }, {
+ renderTo: 'dashboard-col-2',
+ connector: {
+ id: 'xray'
+ },
+ type: 'DataGrid',
+ title: 'Regional Exposure',
+ dataGridOptions: {
+ header: [{
+ format: 'Net',
+ columnId: 'XRay_RegionalExposure_N_Categories'
+ }, {
+ format: 'Values',
+ columnId: 'XRay_RegionalExposure_N_Values'
+ }],
+ columns: [{
+ id: 'XRay_RegionalExposure_N_Categories',
+ cells: {
+ formatter: function () {
+ return this.value !== void 0 ?
+ regionalExposureMap[this.value] : '';
+ }
+ }
+ }]
+ }
+ }
+ ]
+});
+
+ board.dataPool.getConnectorTable('xray');
+
+}
+
+async function handleSelectEnvironment (evt) {
+ const target = evt.target;
+ const postmanJSON = await getPostmanJSON(target);
+
+ target.parentNode.style.display = 'none';
+
+ displaySecurityDetails(postmanJSON);
+}
+
+document.getElementById('postman-json')
+ .addEventListener('change', handleSelectEnvironment);
+
+async function getPostmanJSON (htmlInputFile) {
+ let file;
+ let fileJSON;
+
+ for (file of htmlInputFile.files) {
+ try {
+ fileJSON = JSON.parse(await file.text());
+ if (HighchartsConnectors.Shared.Morningstar.isPostmanEnvironmentJSON(fileJSON)) {
+ break;
+ }
+ } catch (error) {
+ // fail silently
+ }
+ }
+
+ return fileJSON;
+}
diff --git a/demos/index.html b/demos/index.html
index 836ac72..0131f9a 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -8,6 +8,7 @@
Morningstar Connectors Demos
- Highcharts Dashboards + Morningstar RNA News
+ - Highcharts Dashboards + Morningstar XRay Connector
- Highcharts Stock + Morningstar TimeSeries
- Highcharts Stock + Morningstar OHLCV TimeSeries
- Highcharts Stock + Morningstar Security Details
diff --git a/src/Shared/MorningstarOptions.ts b/src/Shared/MorningstarOptions.ts
index 22b58a1..831e418 100644
--- a/src/Shared/MorningstarOptions.ts
+++ b/src/Shared/MorningstarOptions.ts
@@ -210,6 +210,16 @@ interface MorningstarSecurityOptionsGeneric {
*/
type?: (string|MorningstarSecurityType);
+ /**
+ * Type of holding.
+ */
+ holdingType?: string;
+
+ /**
+ * Weight of holding.
+ */
+ weight?: (number|string);
+
}
diff --git a/src/XRay/XRayConnector.ts b/src/XRay/XRayConnector.ts
index 52a8bfa..d33f0e7 100644
--- a/src/XRay/XRayConnector.ts
+++ b/src/XRay/XRayConnector.ts
@@ -47,7 +47,7 @@ interface XRayHoldingObject {
amount?: string;
identifier: string;
identifierType: string;
- holdingType: number;
+ holdingType: string|number;
name?: string;
securityType?: string;
weight?: string;
@@ -78,7 +78,7 @@ function convertHoldings (
const holding: XRayHoldingObject = {
identifier: security.id,
identifierType: security.idType,
- holdingType
+ holdingType: security.holdingType || holdingType
};
if (security.name) {
@@ -89,6 +89,10 @@ function convertHoldings (
holding.securityType = security.type;
}
+ if (security.weight) {
+ holding.weight = security.weight.toString();
+ }
+
return holding;
});
}
diff --git a/src/XRay/XRayConverter.ts b/src/XRay/XRayConverter.ts
index 5441749..f4bd819 100644
--- a/src/XRay/XRayConverter.ts
+++ b/src/XRay/XRayConverter.ts
@@ -125,22 +125,48 @@ export class XRayConverter extends MorningstarConverter {
): void {
const table = this.table;
- for (const asset of json.assetAllocation) {
- const rowId = `${benchmarkId}_${asset.type}_${asset.salePosition}`;
- const values = asset.values;
-
- for (let i = 1; i < 100; ++i) {
- table.setCell(rowId, i - 1, values[i]);
+ if (json.assetAllocation) {
+ for (const asset of json.assetAllocation) {
+ const columnName = `${benchmarkId}_${asset.type}_${asset.salePosition}`;
+ table.setColumn(`${columnName}_Categories`);
+ table.setColumn(`${columnName}_Values`);
+ const values = asset.values;
+
+ const valueIndex = Object.keys(values);
+
+ for (let i = 0; i < valueIndex.length; i++) {
+ table.setCell(`${columnName}_Categories`, i, valueIndex[i]);
+ table.setCell(`${columnName}_Values`, i, values[parseInt(valueIndex[i])]);
+ }
}
}
if (json.regionalExposure) {
for (const exposure of json.regionalExposure) {
- const rowId = `${benchmarkId}_RegionalExposure_${exposure.salePosition}`;
+ const columnName = `${benchmarkId}_RegionalExposure_${exposure.salePosition}`;
+ table.setColumn(`${columnName}_Categories`);
+ table.setColumn(`${columnName}_Values`);
const values = exposure.values;
+ const valueIndex = Object.keys(values);
+
+ for (let i = 0; i < valueIndex.length; i++) {
+ table.setCell(`${columnName}_Categories`, i, valueIndex[i]);
+ table.setCell(`${columnName}_Values`, i, values[parseInt(valueIndex[i])]);
+ }
+ }
+ }
- for (let i = 1; i < 100; ++i) {
- table.setCell(rowId, i - 1, values[i] || 0);
+ if (json.globalStockSector) {
+ for (const sector of json.globalStockSector) {
+ const columnName = `${benchmarkId}_GlobalStockSector_${sector.salePosition}`;
+ table.setColumn(`${columnName}_Categories`);
+ table.setColumn(`${columnName}_Values`);
+ const values = sector.values;
+ const valueIndex = Object.keys(values);
+
+ for (let i = 0; i < valueIndex.length; i++) {
+ table.setCell(`${columnName}_Categories`, i, valueIndex[i]);
+ table.setCell(`${columnName}_Values`, i, values[parseInt(valueIndex[i])]);
}
}
}
diff --git a/src/XRay/XRayJSON.ts b/src/XRay/XRayJSON.ts
index 45b8c81..45eed62 100644
--- a/src/XRay/XRayJSON.ts
+++ b/src/XRay/XRayJSON.ts
@@ -49,6 +49,7 @@ namespace XRayJSON {
export interface Breakdowns {
assetAllocation: Array;
regionalExposure?: Array;
+ globalStockSector?: Array;
}
@@ -72,6 +73,11 @@ namespace XRayJSON {
values: Record;
}
+ export interface GlobalStockSector {
+ salePosition: string;
+ values: Record;
+ }
+
export interface Response {
XRay: Array;
@@ -127,6 +133,17 @@ namespace XRayJSON {
);
}
+ function isGlobalStockSector (
+ json?: unknown
+ ): json is GlobalStockSector {
+ return (
+ !!json &&
+ typeof json === 'object' &&
+ typeof (json as GlobalStockSector).salePosition === 'string' &&
+ typeof (json as GlobalStockSector).values === 'object'
+ );
+ }
+
function isBenchmark (
json?: unknown
@@ -149,6 +166,21 @@ namespace XRayJSON {
);
}
+ function checkRegionalExposure (
+ regionalExposureArray?: unknown
+ ): regionalExposureArray is Array {
+ return (
+ !!regionalExposureArray &&
+ typeof regionalExposureArray === 'object' &&
+ regionalExposureArray instanceof Array &&
+ (
+ regionalExposureArray &&
+ regionalExposureArray.length === 0 ||
+ isRegionalExposure(regionalExposureArray[0])
+ )
+ );
+ }
+
function isBreakdowns (
json?: unknown
@@ -156,14 +188,17 @@ namespace XRayJSON {
return (
!!json &&
typeof json === 'object' &&
+
(json as Breakdowns).assetAllocation instanceof Array &&
(
(json as Breakdowns).assetAllocation.length === 0 ||
isAssetAllocation((json as Breakdowns).assetAllocation[0])
- ) &&
- (
- typeof (json as Breakdowns).regionalExposure === 'undefined' ||
- isRegionalExposure((json as Breakdowns).regionalExposure)
+ ) || (
+ !(json as Breakdowns).regionalExposure ||
+ checkRegionalExposure((json as Breakdowns).regionalExposure)
+ ) || (
+ !(json as Breakdowns).globalStockSector ||
+ isGlobalStockSector((json as Breakdowns).globalStockSector?.[0])
)
);
}