Demo of Perspective.
Example of live market info from IEX Cloud.
| license: apache-2.0 |
Demo of Perspective.
Example of live market info from IEX Cloud.
| perspective-workspace { | |
| flex: 1 | |
| } | |
| #container { | |
| flex: 1; | |
| display: flex; | |
| } | |
| input { | |
| margin: 12px 8px 4px 8px; | |
| padding: 8px; | |
| font-size: 24px; | |
| flex: 0 1; | |
| text-transform: uppercase; | |
| max-width: 600px; | |
| } | |
| perspective-viewer-datagrid regular-table { | |
| overflow-x: hidden; | |
| } | |
| perspective-viewer table { | |
| white-space: nowrap; | |
| } | |
| perspective-viewer tr, | |
| perspective-viewer thead, | |
| perspective-viewer tbody { | |
| display: inline-block; | |
| } | |
| perspective-viewer th, | |
| perspective-viewer td { | |
| display: block; | |
| } | |
| body { | |
| background-color: #eee; | |
| display: flex; | |
| margin: 0; | |
| flex-direction: column; | |
| position: absolute; | |
| top: 0; | |
| left:0; | |
| right: 0; | |
| bottom: 0; | |
| } |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" /> | |
| <script src="https://cdn.jsdelivr.net/npm/@finos/perspective@latest"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@finos/perspective-workspace@latest"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-datagrid@latest"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-d3fc@latest"></script> | |
| <link rel='stylesheet' href="index.css"> | |
| <link rel='stylesheet' crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/@finos/perspective-workspace/dist/css/material.css"> | |
| <script src="index.js"></script> | |
| </head> | |
| <body> | |
| <input id="ticker" type="text" value="JPM"></input> | |
| <div id="container"> | |
| <perspective-workspace id="workspace"> | |
| <perspective-viewer table="query1"></perspective-viewer> | |
| <perspective-viewer table="query2"></perspective-viewer> | |
| <perspective-viewer table="query3"></perspective-viewer> | |
| </perspective-workspace> | |
| </div> | |
| </body> | |
| </html> |
| const PUB_TOKEN = "Tpk_ecc89ddf30a611e9958142010a80043c"; | |
| const IEX_URL = "https://sandbox.iexapis.com/beta/stock/"; | |
| const QUERY1 = "/chart?token=" + PUB_TOKEN; | |
| const QUERY2 = "/stats?token=" + PUB_TOKEN; | |
| const QUERY3 = "/balance-sheet?token=" + PUB_TOKEN; | |
| const worker = perspective.shared_worker(); | |
| const TABLES = new Map(); | |
| async function run(input, string, wrap = false, index = null) { | |
| const req = await fetch(IEX_URL + input.value + string); | |
| let data = await req.json(); | |
| if (index) { | |
| data = data[index]; | |
| } | |
| if (wrap) { | |
| data = [data]; | |
| } | |
| let table; | |
| if (TABLES.has(string)) { | |
| table = TABLES.get(string); | |
| table.clear(); | |
| table.update(data); | |
| } else { | |
| table = await worker.table(data); | |
| TABLES.set(string, table); | |
| } | |
| return table; | |
| } | |
| async function get_layout() { | |
| const req = await fetch("layout.json"); | |
| const json = await req.json(); | |
| return json; | |
| } | |
| window.addEventListener("DOMContentLoaded", async function () { | |
| const workspace = document.getElementsByTagName("perspective-workspace")[0]; | |
| const input = document.getElementById("ticker"); | |
| input.addEventListener("keydown", async (ev) => { | |
| input.style.color = "inherit"; | |
| if (ev.keyCode === 13) { | |
| try { | |
| await run(input, QUERY1); | |
| await run(input, QUERY2, true); | |
| await run(input, QUERY3, false, "balancesheet"); | |
| } catch (e) { | |
| input.style.color = "red"; | |
| } | |
| } | |
| }); | |
| workspace.addTable("query1", run(input, QUERY1)); | |
| workspace.addTable("query2", run(input, QUERY2, true)); | |
| workspace.addTable("query3", run(input, QUERY3, false, "balancesheet")); | |
| customElements.get("perspective-viewer-datagrid").prototype.virtual_mode = | |
| "none"; | |
| const to_span = (x) => `<span style='color:#666'>${x}</span>`; | |
| workspace.addEventListener("perspective-datagrid-after-update", (event) => { | |
| const form = new Intl.NumberFormat([], {}); | |
| const datagrid = event.detail; | |
| for (const td of datagrid.get_tds()) { | |
| const metadata = datagrid.get_meta(td); | |
| if (metadata.column.endsWith("Percent")) { | |
| td.innerHTML = | |
| form.format(Math.round(metadata.value * 100)) + | |
| to_span(" %"); | |
| } else if ( | |
| metadata.type === "float" && | |
| (metadata.value < -1000000 || metadata.value > 1000000) | |
| ) { | |
| td.innerHTML = | |
| form.format(Math.round(metadata.value / 1000000)) + | |
| to_span(" mm"); | |
| } | |
| } | |
| }); | |
| const json = await get_layout(); | |
| workspace.restore(json); | |
| }); |
| { | |
| "sizes": [1], | |
| "detail": { | |
| "main": { | |
| "type": "split-area", | |
| "orientation": "horizontal", | |
| "children": [ | |
| { | |
| "type": "tab-area", | |
| "widgets": [ | |
| "PERSPECTIVE_GENERATED_ID_1", | |
| "PERSPECTIVE_GENERATED_ID_2" | |
| ], | |
| "currentIndex": 0 | |
| }, | |
| { | |
| "type": "tab-area", | |
| "widgets": [ | |
| "PERSPECTIVE_GENERATED_ID_5", | |
| "PERSPECTIVE_GENERATED_ID_3" | |
| ], | |
| "currentIndex": 0 | |
| } | |
| ], | |
| "sizes": [0.4, 0.6] | |
| } | |
| }, | |
| "mode": "globalFilters", | |
| "viewers": { | |
| "PERSPECTIVE_GENERATED_ID_1": { | |
| "plugin": "datagrid", | |
| "columns": [ | |
| "companyName", | |
| "nextDividendDate", | |
| "nextEarningsDate", | |
| "exDividendDate", | |
| "week52change", | |
| "week52high", | |
| "week52low", | |
| "marketcap", | |
| "employees", | |
| "day200MovingAvg", | |
| "day50MovingAvg", | |
| "float", | |
| "avg10Volume", | |
| "avg30Volume", | |
| "ttmEPS", | |
| "ttmDividendRate", | |
| "sharesOutstanding", | |
| "maxChangePercent", | |
| "year5ChangePercent", | |
| "year2ChangePercent", | |
| "year1ChangePercent", | |
| "ytdChangePercent", | |
| "month6ChangePercent", | |
| "month3ChangePercent", | |
| "month1ChangePercent", | |
| "day30ChangePercent", | |
| "day5ChangePercent", | |
| "dividendYield", | |
| "peRatio", | |
| "beta" | |
| ], | |
| "master": false, | |
| "name": "Reference", | |
| "table": "query2", | |
| "linked": false | |
| }, | |
| "PERSPECTIVE_GENERATED_ID_2": { | |
| "plugin": "datagrid", | |
| "columns": [ | |
| "reportDate", | |
| "fiscalDate", | |
| "currency", | |
| "currentCash", | |
| "shortTermInvestments", | |
| "receivables", | |
| "inventory", | |
| "otherCurrentAssets", | |
| "currentAssets", | |
| "longTermInvestments", | |
| "propertyPlantEquipment", | |
| "goodwill", | |
| "intangibleAssets", | |
| "otherAssets", | |
| "totalAssets", | |
| "accountsPayable", | |
| "currentLongTermDebt", | |
| "otherCurrentLiabilities", | |
| "totalCurrentLiabilities", | |
| "longTermDebt", | |
| "otherLiabilities", | |
| "minorityInterest", | |
| "totalLiabilities", | |
| "commonStock", | |
| "retainedEarnings", | |
| "treasuryStock", | |
| "capitalSurplus", | |
| "shareholderEquity", | |
| "netTangibleAssets" | |
| ], | |
| "master": false, | |
| "name": "Balance Sheet", | |
| "table": "query3", | |
| "linked": false | |
| }, | |
| "PERSPECTIVE_GENERATED_ID_5": { | |
| "plugin": "Candlestick", | |
| "columns": ["open", "close", "high", "low"], | |
| "group_by": ["date"], | |
| "plugin_config": { | |
| "realValues": ["open", "close", "high", "low"] | |
| }, | |
| "master": false, | |
| "name": "Px", | |
| "table": "query1", | |
| "linked": false | |
| }, | |
| "PERSPECTIVE_GENERATED_ID_3": { | |
| "plugin": "Y Bar", | |
| "columns": ["uVolume"], | |
| "plugin_config": { | |
| "realValues": ["uVolume"] | |
| }, | |
| "master": false, | |
| "name": "Volume", | |
| "table": "query1", | |
| "linked": false | |
| } | |
| } | |
| } |