qsharp-lang 1.0.23-dev → 1.0.25-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.ts +2 -1
- package/dist/compiler/compiler.d.ts +2 -0
- package/dist/compiler/compiler.js +3 -0
- package/dist/compiler/events.d.ts +0 -1
- package/dist/compiler/events.js +3 -6
- package/dist/compiler/worker-proxy.js +1 -0
- package/dist/debug-service/debug-service.d.ts +3 -2
- package/dist/katas-content.generated.js +2 -2
- package/dist/language-service/language-service.d.ts +3 -3
- package/dist/language-service/language-service.js +2 -2
- package/dist/samples.generated.js +7 -7
- package/lib/node/qsc_wasm.cjs +37 -2
- package/lib/node/qsc_wasm.d.cts +18 -3
- package/lib/node/qsc_wasm_bg.wasm +0 -0
- package/lib/web/qsc_wasm.d.ts +20 -4
- package/lib/web/qsc_wasm.js +37 -2
- package/lib/web/qsc_wasm_bg.wasm +0 -0
- package/package.json +5 -3
- package/ux/README.md +4 -0
- package/ux/histogram.tsx +438 -0
- package/ux/index.ts +11 -0
- package/ux/qsharp-ux.css +616 -0
- package/ux/reTable.tsx +113 -0
- package/ux/resultsTable.tsx +385 -0
- package/ux/spaceChart.tsx +148 -0
- package/ux/tsconfig.json +15 -0
package/lib/node/qsc_wasm.d.cts
CHANGED
|
@@ -19,6 +19,12 @@ export function git_hash(): string;
|
|
|
19
19
|
*/
|
|
20
20
|
export function get_qir(code: string): string;
|
|
21
21
|
/**
|
|
22
|
+
* @param {string} code
|
|
23
|
+
* @param {string} params
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function get_estimates(code: string, params: string): string;
|
|
27
|
+
/**
|
|
22
28
|
* @param {string} name
|
|
23
29
|
* @returns {string | undefined}
|
|
24
30
|
*/
|
|
@@ -128,7 +134,7 @@ export interface IRelatedInformation {
|
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
export interface IWorkspaceConfiguration {
|
|
131
|
-
targetProfile?:
|
|
137
|
+
targetProfile?: TargetProfile;
|
|
132
138
|
packageType?: "exe" | "lib";
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -138,7 +144,7 @@ export interface ICompletionList {
|
|
|
138
144
|
|
|
139
145
|
export interface ICompletionItem {
|
|
140
146
|
label: string;
|
|
141
|
-
kind: "function" | "interface" | "keyword" | "module" | "property";
|
|
147
|
+
kind: "function" | "interface" | "keyword" | "module" | "property" | "variable" | "typeParameter";
|
|
142
148
|
sortText?: string;
|
|
143
149
|
detail?: string;
|
|
144
150
|
additionalTextEdits?: ITextEdit[];
|
|
@@ -191,6 +197,14 @@ export interface ICell {
|
|
|
191
197
|
code: string;
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
export interface INotebookMetadata {
|
|
201
|
+
targetProfile?: "unrestricted" | "base";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
export type TargetProfile = "base" | "unrestricted";
|
|
206
|
+
|
|
207
|
+
|
|
194
208
|
/**
|
|
195
209
|
*/
|
|
196
210
|
export class DebugService {
|
|
@@ -272,9 +286,10 @@ export class LanguageService {
|
|
|
272
286
|
close_document(uri: string): void;
|
|
273
287
|
/**
|
|
274
288
|
* @param {string} notebook_uri
|
|
289
|
+
* @param {INotebookMetadata} notebook_metadata
|
|
275
290
|
* @param {(ICell)[]} cells
|
|
276
291
|
*/
|
|
277
|
-
update_notebook_document(notebook_uri: string, cells: (ICell)[]): void;
|
|
292
|
+
update_notebook_document(notebook_uri: string, notebook_metadata: INotebookMetadata, cells: (ICell)[]): void;
|
|
278
293
|
/**
|
|
279
294
|
* @param {string} notebook_uri
|
|
280
295
|
* @param {(string)[]} cell_uris
|
|
Binary file
|
package/lib/web/qsc_wasm.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export function git_hash(): string;
|
|
|
19
19
|
*/
|
|
20
20
|
export function get_qir(code: string): string;
|
|
21
21
|
/**
|
|
22
|
+
* @param {string} code
|
|
23
|
+
* @param {string} params
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function get_estimates(code: string, params: string): string;
|
|
27
|
+
/**
|
|
22
28
|
* @param {string} name
|
|
23
29
|
* @returns {string | undefined}
|
|
24
30
|
*/
|
|
@@ -128,7 +134,7 @@ export interface IRelatedInformation {
|
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
export interface IWorkspaceConfiguration {
|
|
131
|
-
targetProfile?:
|
|
137
|
+
targetProfile?: TargetProfile;
|
|
132
138
|
packageType?: "exe" | "lib";
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -138,7 +144,7 @@ export interface ICompletionList {
|
|
|
138
144
|
|
|
139
145
|
export interface ICompletionItem {
|
|
140
146
|
label: string;
|
|
141
|
-
kind: "function" | "interface" | "keyword" | "module" | "property";
|
|
147
|
+
kind: "function" | "interface" | "keyword" | "module" | "property" | "variable" | "typeParameter";
|
|
142
148
|
sortText?: string;
|
|
143
149
|
detail?: string;
|
|
144
150
|
additionalTextEdits?: ITextEdit[];
|
|
@@ -191,6 +197,14 @@ export interface ICell {
|
|
|
191
197
|
code: string;
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
export interface INotebookMetadata {
|
|
201
|
+
targetProfile?: "unrestricted" | "base";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
export type TargetProfile = "base" | "unrestricted";
|
|
206
|
+
|
|
207
|
+
|
|
194
208
|
/**
|
|
195
209
|
*/
|
|
196
210
|
export class DebugService {
|
|
@@ -272,9 +286,10 @@ export class LanguageService {
|
|
|
272
286
|
close_document(uri: string): void;
|
|
273
287
|
/**
|
|
274
288
|
* @param {string} notebook_uri
|
|
289
|
+
* @param {INotebookMetadata} notebook_metadata
|
|
275
290
|
* @param {(ICell)[]} cells
|
|
276
291
|
*/
|
|
277
|
-
update_notebook_document(notebook_uri: string, cells: (ICell)[]): void;
|
|
292
|
+
update_notebook_document(notebook_uri: string, notebook_metadata: INotebookMetadata, cells: (ICell)[]): void;
|
|
278
293
|
/**
|
|
279
294
|
* @param {string} notebook_uri
|
|
280
295
|
* @param {(string)[]} cell_uris
|
|
@@ -346,7 +361,7 @@ export interface InitOutput {
|
|
|
346
361
|
readonly languageservice_update_configuration: (a: number, b: number) => void;
|
|
347
362
|
readonly languageservice_update_document: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
|
348
363
|
readonly languageservice_close_document: (a: number, b: number, c: number) => void;
|
|
349
|
-
readonly languageservice_update_notebook_document: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
364
|
+
readonly languageservice_update_notebook_document: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
|
|
350
365
|
readonly languageservice_close_notebook_document: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
351
366
|
readonly languageservice_get_completions: (a: number, b: number, c: number, d: number) => number;
|
|
352
367
|
readonly languageservice_get_definition: (a: number, b: number, c: number, d: number) => number;
|
|
@@ -359,6 +374,7 @@ export interface InitOutput {
|
|
|
359
374
|
readonly setLogLevel: (a: number) => void;
|
|
360
375
|
readonly git_hash: (a: number) => void;
|
|
361
376
|
readonly get_qir: (a: number, b: number, c: number) => void;
|
|
377
|
+
readonly get_estimates: (a: number, b: number, c: number, d: number, e: number) => void;
|
|
362
378
|
readonly get_library_source_content: (a: number, b: number, c: number) => void;
|
|
363
379
|
readonly get_hir: (a: number, b: number, c: number) => void;
|
|
364
380
|
readonly run: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
|
package/lib/web/qsc_wasm.js
CHANGED
|
@@ -309,6 +309,40 @@ export function get_qir(code) {
|
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
/**
|
|
313
|
+
* @param {string} code
|
|
314
|
+
* @param {string} params
|
|
315
|
+
* @returns {string}
|
|
316
|
+
*/
|
|
317
|
+
export function get_estimates(code, params) {
|
|
318
|
+
let deferred4_0;
|
|
319
|
+
let deferred4_1;
|
|
320
|
+
try {
|
|
321
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
322
|
+
const ptr0 = passStringToWasm0(code, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);
|
|
323
|
+
const len0 = WASM_VECTOR_LEN;
|
|
324
|
+
const ptr1 = passStringToWasm0(params, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);
|
|
325
|
+
const len1 = WASM_VECTOR_LEN;
|
|
326
|
+
wasm.get_estimates(retptr, ptr0, len0, ptr1, len1);
|
|
327
|
+
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
|
328
|
+
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
|
329
|
+
var r2 = getInt32Memory0()[retptr / 4 + 2];
|
|
330
|
+
var r3 = getInt32Memory0()[retptr / 4 + 3];
|
|
331
|
+
var ptr3 = r0;
|
|
332
|
+
var len3 = r1;
|
|
333
|
+
if (r3) {
|
|
334
|
+
ptr3 = 0; len3 = 0;
|
|
335
|
+
throw takeObject(r2);
|
|
336
|
+
}
|
|
337
|
+
deferred4_0 = ptr3;
|
|
338
|
+
deferred4_1 = len3;
|
|
339
|
+
return getStringFromWasm0(ptr3, len3);
|
|
340
|
+
} finally {
|
|
341
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
342
|
+
wasm.__wbindgen_export_2(deferred4_0, deferred4_1, 1);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
312
346
|
/**
|
|
313
347
|
* @param {string} name
|
|
314
348
|
* @returns {string | undefined}
|
|
@@ -652,14 +686,15 @@ export class LanguageService {
|
|
|
652
686
|
}
|
|
653
687
|
/**
|
|
654
688
|
* @param {string} notebook_uri
|
|
689
|
+
* @param {INotebookMetadata} notebook_metadata
|
|
655
690
|
* @param {(ICell)[]} cells
|
|
656
691
|
*/
|
|
657
|
-
update_notebook_document(notebook_uri, cells) {
|
|
692
|
+
update_notebook_document(notebook_uri, notebook_metadata, cells) {
|
|
658
693
|
const ptr0 = passStringToWasm0(notebook_uri, wasm.__wbindgen_export_0, wasm.__wbindgen_export_1);
|
|
659
694
|
const len0 = WASM_VECTOR_LEN;
|
|
660
695
|
const ptr1 = passArrayJsValueToWasm0(cells, wasm.__wbindgen_export_0);
|
|
661
696
|
const len1 = WASM_VECTOR_LEN;
|
|
662
|
-
wasm.languageservice_update_notebook_document(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
|
697
|
+
wasm.languageservice_update_notebook_document(this.__wbg_ptr, ptr0, len0, addHeapObject(notebook_metadata), ptr1, len1);
|
|
663
698
|
}
|
|
664
699
|
/**
|
|
665
700
|
* @param {string} notebook_uri
|
package/lib/web/qsc_wasm_bg.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qsharp-lang",
|
|
3
3
|
"description": "qsharp language package for quantum development",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.25-dev",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=16.17.0"
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
},
|
|
20
20
|
"./compiler-worker": "./dist/compiler/worker-browser.js",
|
|
21
21
|
"./language-service-worker": "./dist/language-service/worker-browser.js",
|
|
22
|
-
"./debug-service-worker": "./dist/debug-service/worker-browser.js"
|
|
22
|
+
"./debug-service-worker": "./dist/debug-service/worker-browser.js",
|
|
23
|
+
"./ux": "./ux/index.ts"
|
|
23
24
|
},
|
|
24
25
|
"scripts": {
|
|
25
26
|
"build": "npm run generate && npm run build:tsc",
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
"type": "module",
|
|
32
33
|
"files": [
|
|
33
34
|
"dist",
|
|
34
|
-
"lib"
|
|
35
|
+
"lib",
|
|
36
|
+
"ux"
|
|
35
37
|
]
|
|
36
38
|
}
|
package/ux/README.md
ADDED
package/ux/histogram.tsx
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import { useRef, useState } from "preact/hooks";
|
|
5
|
+
|
|
6
|
+
const enablePanning = false;
|
|
7
|
+
const altKeyPans = true;
|
|
8
|
+
|
|
9
|
+
const menuItems = [
|
|
10
|
+
{
|
|
11
|
+
category: "itemCount",
|
|
12
|
+
options: ["Show all", "Top 10", "Top 25"],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
category: "sortOrder",
|
|
16
|
+
options: ["Sort a-z", "High to low", "Low to high"],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
category: "labels",
|
|
20
|
+
options: ["Raw labels", "Ket labels", "No labels"],
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const maxMenuOptions = 3;
|
|
24
|
+
const defaultMenuSelection: { [idx: string]: number } = {
|
|
25
|
+
itemCount: 0,
|
|
26
|
+
sortOrder: 0,
|
|
27
|
+
labels: 0,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const reKetResult = /^\[(?:(Zero|One), *)*(Zero|One)\]$/;
|
|
31
|
+
function resultToKet(result: string): string {
|
|
32
|
+
if (typeof result !== "string") return "ERROR";
|
|
33
|
+
|
|
34
|
+
if (reKetResult.test(result)) {
|
|
35
|
+
// The result is a simple array of Zero and One
|
|
36
|
+
// The below will return an array of "Zero" or "One" in the order found
|
|
37
|
+
const matches = result.match(/(One|Zero)/g);
|
|
38
|
+
matches?.reverse();
|
|
39
|
+
let ket = "|";
|
|
40
|
+
matches?.forEach((digit) => (ket += digit == "One" ? "1" : "0"));
|
|
41
|
+
ket += "⟩";
|
|
42
|
+
return ket;
|
|
43
|
+
} else {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function Histogram(props: {
|
|
49
|
+
shotCount: number;
|
|
50
|
+
data: Map<string, number>;
|
|
51
|
+
filter: string;
|
|
52
|
+
onFilter: (filter: string) => void;
|
|
53
|
+
shotsHeader: boolean;
|
|
54
|
+
}) {
|
|
55
|
+
const [hoverLabel, setHoverLabel] = useState("");
|
|
56
|
+
const [scale, setScale] = useState({ zoom: 1.0, offset: 1.0 });
|
|
57
|
+
const [menuSelection, setMenuSelection] = useState(defaultMenuSelection);
|
|
58
|
+
|
|
59
|
+
const gMenu = useRef<SVGGElement>(null);
|
|
60
|
+
const gInfo = useRef<SVGGElement>(null);
|
|
61
|
+
|
|
62
|
+
let maxItemsToShow = 0; // All
|
|
63
|
+
switch (menuSelection["itemCount"]) {
|
|
64
|
+
case 1:
|
|
65
|
+
maxItemsToShow = 10;
|
|
66
|
+
break;
|
|
67
|
+
case 2:
|
|
68
|
+
maxItemsToShow = 25;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
const showKetLabels = menuSelection["labels"] === 1;
|
|
72
|
+
|
|
73
|
+
const bucketArray = [...props.data];
|
|
74
|
+
|
|
75
|
+
// Calculate bucket percentages before truncating for display
|
|
76
|
+
let totalAllBuckets = 0;
|
|
77
|
+
let sizeBiggestBucket = 0;
|
|
78
|
+
bucketArray.forEach((x) => {
|
|
79
|
+
totalAllBuckets += x[1];
|
|
80
|
+
sizeBiggestBucket = Math.max(x[1], sizeBiggestBucket);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let histogramLabel = `${bucketArray.length} unique results`;
|
|
84
|
+
if (maxItemsToShow > 0) {
|
|
85
|
+
// Sort from high to low then take the first n
|
|
86
|
+
bucketArray.sort((a, b) => (a[1] < b[1] ? 1 : -1));
|
|
87
|
+
if (bucketArray.length > maxItemsToShow) {
|
|
88
|
+
histogramLabel = `Top ${maxItemsToShow} of ${histogramLabel}`;
|
|
89
|
+
bucketArray.length = maxItemsToShow;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (props.filter) {
|
|
93
|
+
histogramLabel += `. Shot filter: ${
|
|
94
|
+
showKetLabels ? resultToKet(props.filter) : props.filter
|
|
95
|
+
}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
bucketArray.sort((a, b) => {
|
|
99
|
+
// If they can be converted to numbers, then sort as numbers, else lexically
|
|
100
|
+
const ax = Number(a[0]);
|
|
101
|
+
const bx = Number(b[0]);
|
|
102
|
+
switch (menuSelection["sortOrder"]) {
|
|
103
|
+
case 1: // high-to-low
|
|
104
|
+
return a[1] < b[1] ? 1 : -1;
|
|
105
|
+
break;
|
|
106
|
+
case 2: // low-to-high
|
|
107
|
+
return a[1] > b[1] ? 1 : -1;
|
|
108
|
+
break;
|
|
109
|
+
default: // a-z
|
|
110
|
+
if (!isNaN(ax) && !isNaN(bx)) return ax < bx ? -1 : 1;
|
|
111
|
+
return a[0] < b[0] ? -1 : 1;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function onMouseOverRect(evt: MouseEvent) {
|
|
117
|
+
const target = evt.target as SVGRectElement;
|
|
118
|
+
const title = target.querySelector("title")?.textContent;
|
|
119
|
+
setHoverLabel(title || "");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function onMouseOutRect() {
|
|
123
|
+
setHoverLabel("");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function onClickRect(evt: MouseEvent) {
|
|
127
|
+
const targetElem = evt.target as SVGRectElement;
|
|
128
|
+
const rawLabel = targetElem.getAttribute("data-raw-label");
|
|
129
|
+
|
|
130
|
+
if (rawLabel === props.filter) {
|
|
131
|
+
// Clicked the already selected bar. Clear the filter
|
|
132
|
+
props.onFilter("");
|
|
133
|
+
} else {
|
|
134
|
+
props.onFilter(rawLabel || "");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function toggleMenu() {
|
|
139
|
+
if (!gMenu.current) return;
|
|
140
|
+
if (gMenu.current.style.display === "inline") {
|
|
141
|
+
gMenu.current.style.display = "none";
|
|
142
|
+
} else {
|
|
143
|
+
gMenu.current.style.display = "inline";
|
|
144
|
+
if (gInfo.current) gInfo.current.style.display = "none";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function menuClicked(category: string, idx: number) {
|
|
149
|
+
if (!gMenu.current) return;
|
|
150
|
+
const newMenuSelection = { ...menuSelection };
|
|
151
|
+
newMenuSelection[category] = idx;
|
|
152
|
+
setMenuSelection(newMenuSelection);
|
|
153
|
+
if (category === "itemCount") {
|
|
154
|
+
setScale({ zoom: 1, offset: 1 });
|
|
155
|
+
}
|
|
156
|
+
gMenu.current.style.display = "none";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function toggleInfo() {
|
|
160
|
+
if (!gInfo.current) return;
|
|
161
|
+
|
|
162
|
+
gInfo.current.style.display === "inline"
|
|
163
|
+
? (gInfo.current.style.display = "none")
|
|
164
|
+
: (gInfo.current.style.display = "inline");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Each menu item has a width of 32px and a height of 10px
|
|
168
|
+
// Menu items are 38px apart on the x-axis, and 11px on the y-axis.
|
|
169
|
+
const menuItemWidth = 38;
|
|
170
|
+
const menuItemHeight = 11;
|
|
171
|
+
const menuBoxWidth = menuItems.length * menuItemWidth - 2;
|
|
172
|
+
const menuBoxHeight = maxMenuOptions * menuItemHeight + 3;
|
|
173
|
+
|
|
174
|
+
const barAreaWidth = 163;
|
|
175
|
+
const barAreaHeight = 72;
|
|
176
|
+
const fontOffset = 1.2;
|
|
177
|
+
|
|
178
|
+
// Scale the below for when zoomed
|
|
179
|
+
const barBoxWidth = (barAreaWidth * scale.zoom) / bucketArray.length;
|
|
180
|
+
const barPaddingPercent = 0.1; // 10%
|
|
181
|
+
const barPaddingSize = barBoxWidth * barPaddingPercent;
|
|
182
|
+
const barFillWidth = barBoxWidth - 2 * barPaddingSize;
|
|
183
|
+
const showLabels = barBoxWidth > 5 && menuSelection["labels"] !== 2;
|
|
184
|
+
|
|
185
|
+
function onWheel(e: WheelEvent): void {
|
|
186
|
+
e.preventDefault();
|
|
187
|
+
|
|
188
|
+
// currentTarget is the element the listener is attached to, the main svg
|
|
189
|
+
// element in this case.
|
|
190
|
+
const svgElem = e.currentTarget as SVGSVGElement;
|
|
191
|
+
|
|
192
|
+
// Below gets the mouse location in the svg element coordinates. This stays
|
|
193
|
+
// consistent while the scroll is occuring (i.e. it is the point the mouse
|
|
194
|
+
// was at when scrolling started).
|
|
195
|
+
const mousePoint = new DOMPoint(e.clientX, e.clientY).matrixTransform(
|
|
196
|
+
svgElem.getScreenCTM()?.inverse(),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
/*
|
|
200
|
+
While zooming, we want is to track the point the mouse is at when scrolling, and pin
|
|
201
|
+
that location on the screen. That means adjusting the scroll offset.
|
|
202
|
+
|
|
203
|
+
SVG translation is used to pan left and right, but zooming is done manually (making the
|
|
204
|
+
bars wider or thinner) to keep the fonts from getting streched, which occurs with scaling.
|
|
205
|
+
|
|
206
|
+
deltaX and deltaY do not accumulate across events, they are a new delta each time.
|
|
207
|
+
*/
|
|
208
|
+
|
|
209
|
+
let newScrollOffset = scale.offset;
|
|
210
|
+
let newZoom = scale.zoom;
|
|
211
|
+
|
|
212
|
+
// *** First handle any zooming ***
|
|
213
|
+
if (!altKeyPans || !e.altKey) {
|
|
214
|
+
newZoom = scale.zoom + e.deltaY * 0.05;
|
|
215
|
+
newZoom = Math.min(Math.max(1, newZoom), 50);
|
|
216
|
+
|
|
217
|
+
// On zooming in, need to shift left to maintain mouse point, and vice verca.
|
|
218
|
+
const oldChartWidth = barAreaWidth * scale.zoom;
|
|
219
|
+
const mousePointOnChart = 0 - scale.offset + mousePoint.x;
|
|
220
|
+
const percentRightOnChart = mousePointOnChart / oldChartWidth;
|
|
221
|
+
const chartWidthGrowth =
|
|
222
|
+
newZoom * barAreaWidth - scale.zoom * barAreaWidth;
|
|
223
|
+
const shiftLeftAdjust = percentRightOnChart * chartWidthGrowth;
|
|
224
|
+
newScrollOffset = scale.offset - shiftLeftAdjust;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// *** Then handle any panning ***
|
|
228
|
+
if (enablePanning) {
|
|
229
|
+
newScrollOffset -= e.deltaX;
|
|
230
|
+
}
|
|
231
|
+
if (!enablePanning && altKeyPans && e.altKey) {
|
|
232
|
+
newScrollOffset -= e.deltaY;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Don't allow offset > 1 (scrolls the first bar right of the left edge of the area)
|
|
236
|
+
// Don't allow for less than 0 - barwidths + screen width (scrolls last bar left of the right edge)
|
|
237
|
+
const maxScrollRight = 1 - (barAreaWidth * newZoom - barAreaWidth);
|
|
238
|
+
const boundScrollOffset = Math.min(
|
|
239
|
+
Math.max(newScrollOffset, maxScrollRight),
|
|
240
|
+
1,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
setScale({ zoom: newZoom, offset: boundScrollOffset });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<>
|
|
248
|
+
{props.shotsHeader ? (
|
|
249
|
+
<h4 style="margin: 8px 0px">Total shots: {props.shotCount}</h4>
|
|
250
|
+
) : null}
|
|
251
|
+
<svg class="histogram" viewBox="0 0 165 100" onWheel={onWheel}>
|
|
252
|
+
<g transform={`translate(${scale.offset},4)`}>
|
|
253
|
+
{bucketArray.map((entry, idx) => {
|
|
254
|
+
const label = showKetLabels ? resultToKet(entry[0]) : entry[0];
|
|
255
|
+
|
|
256
|
+
const height = barAreaHeight * (entry[1] / sizeBiggestBucket);
|
|
257
|
+
const x = barBoxWidth * idx + barPaddingSize;
|
|
258
|
+
const labelX = barBoxWidth * idx + barBoxWidth / 2 - fontOffset;
|
|
259
|
+
const y = barAreaHeight + 15 - height;
|
|
260
|
+
const barLabel = `${label} at ${(
|
|
261
|
+
(entry[1] / totalAllBuckets) *
|
|
262
|
+
100
|
|
263
|
+
).toFixed(2)}%`;
|
|
264
|
+
let barClass = "bar";
|
|
265
|
+
|
|
266
|
+
if (entry[0] === props.filter) {
|
|
267
|
+
barClass += " bar-selected";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<>
|
|
272
|
+
<rect
|
|
273
|
+
class={barClass}
|
|
274
|
+
x={x}
|
|
275
|
+
y={y}
|
|
276
|
+
width={barFillWidth}
|
|
277
|
+
height={height}
|
|
278
|
+
onMouseOver={onMouseOverRect}
|
|
279
|
+
onMouseOut={onMouseOutRect}
|
|
280
|
+
onClick={onClickRect}
|
|
281
|
+
data-raw-label={entry[0]}
|
|
282
|
+
>
|
|
283
|
+
<title>{barLabel}</title>
|
|
284
|
+
</rect>
|
|
285
|
+
{
|
|
286
|
+
<text
|
|
287
|
+
class="bar-label"
|
|
288
|
+
x={labelX}
|
|
289
|
+
y="85"
|
|
290
|
+
visibility={showLabels ? "visible" : "hidden"}
|
|
291
|
+
transform={`rotate(90, ${labelX}, 85)`}
|
|
292
|
+
>
|
|
293
|
+
{label}
|
|
294
|
+
</text>
|
|
295
|
+
}
|
|
296
|
+
</>
|
|
297
|
+
);
|
|
298
|
+
})}
|
|
299
|
+
</g>
|
|
300
|
+
|
|
301
|
+
<text class="histo-label" x="2" y="97">
|
|
302
|
+
{histogramLabel}
|
|
303
|
+
</text>
|
|
304
|
+
<text class="hover-text" x="85" y="6">
|
|
305
|
+
{hoverLabel}
|
|
306
|
+
</text>
|
|
307
|
+
|
|
308
|
+
{/* The settings icon */}
|
|
309
|
+
<g
|
|
310
|
+
class="menu-icon"
|
|
311
|
+
transform="translate(2, 2) scale(0.3 0.3)"
|
|
312
|
+
onClick={toggleMenu}
|
|
313
|
+
>
|
|
314
|
+
<rect width="24" height="24" fill="white" stroke-widths="0.5"></rect>
|
|
315
|
+
<path
|
|
316
|
+
d="M3 5 H21 M3 12 H21 M3 19 H21"
|
|
317
|
+
stroke-width="1.75"
|
|
318
|
+
stroke-linecap="round"
|
|
319
|
+
/>
|
|
320
|
+
<rect x="6" y="3" width="4" height="4" rx="1" stroke-width="1.5" />
|
|
321
|
+
<rect x="15" y="10" width="4" height="4" rx="1" stroke-width="1.5" />
|
|
322
|
+
<rect x="9" y="17" width="4" height="4" rx="1" stroke-width="1.5" />
|
|
323
|
+
</g>
|
|
324
|
+
|
|
325
|
+
{/* The info icon */}
|
|
326
|
+
<g
|
|
327
|
+
class="menu-icon"
|
|
328
|
+
transform="translate(156, 2) scale(0.3 0.3)"
|
|
329
|
+
onClick={toggleInfo}
|
|
330
|
+
>
|
|
331
|
+
<rect width="24" height="24" stroke-width="0"></rect>
|
|
332
|
+
<circle cx="12" cy="13" r="10" stroke-width="1.5" />
|
|
333
|
+
<path
|
|
334
|
+
stroke-width="2.5"
|
|
335
|
+
stroke-linecap="round"
|
|
336
|
+
d="M12 8 V8 M12 12.5 V18"
|
|
337
|
+
/>
|
|
338
|
+
</g>
|
|
339
|
+
|
|
340
|
+
{/* The menu box */}
|
|
341
|
+
<g
|
|
342
|
+
id="menu"
|
|
343
|
+
ref={gMenu}
|
|
344
|
+
transform="translate(8, 2)"
|
|
345
|
+
style="display: none;"
|
|
346
|
+
>
|
|
347
|
+
<rect
|
|
348
|
+
x="0"
|
|
349
|
+
y="0"
|
|
350
|
+
rx="2"
|
|
351
|
+
width={menuBoxWidth}
|
|
352
|
+
height={menuBoxHeight}
|
|
353
|
+
class="menu-box"
|
|
354
|
+
></rect>
|
|
355
|
+
|
|
356
|
+
{
|
|
357
|
+
// Menu items
|
|
358
|
+
menuItems.map((item, col) => {
|
|
359
|
+
return item.options.map((option, row) => {
|
|
360
|
+
let classList = "menu-item";
|
|
361
|
+
if (menuSelection[item.category] === row)
|
|
362
|
+
classList += " menu-selected";
|
|
363
|
+
return (
|
|
364
|
+
<>
|
|
365
|
+
<rect
|
|
366
|
+
x={2 + col * menuItemWidth}
|
|
367
|
+
y={2 + row * menuItemHeight}
|
|
368
|
+
rx="1"
|
|
369
|
+
class={classList}
|
|
370
|
+
onClick={() => menuClicked(item.category, row)}
|
|
371
|
+
></rect>
|
|
372
|
+
<text
|
|
373
|
+
x={5 + col * menuItemWidth}
|
|
374
|
+
y={9 + row * menuItemHeight}
|
|
375
|
+
class="menu-text"
|
|
376
|
+
>
|
|
377
|
+
{option}
|
|
378
|
+
</text>
|
|
379
|
+
</>
|
|
380
|
+
);
|
|
381
|
+
});
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
{
|
|
385
|
+
// Column separators
|
|
386
|
+
menuItems.map((item, idx) => {
|
|
387
|
+
return idx >= menuItems.length - 1 ? null : (
|
|
388
|
+
<line
|
|
389
|
+
class="menu-separator"
|
|
390
|
+
x1={37 + idx * menuItemWidth}
|
|
391
|
+
y1="2"
|
|
392
|
+
x2={37 + idx * menuItemWidth}
|
|
393
|
+
y2={maxMenuOptions * menuItemHeight + 1}
|
|
394
|
+
></line>
|
|
395
|
+
);
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
</g>
|
|
399
|
+
|
|
400
|
+
{/* The info box */}
|
|
401
|
+
<g ref={gInfo} style="display: none;">
|
|
402
|
+
<rect
|
|
403
|
+
width="155"
|
|
404
|
+
height="76"
|
|
405
|
+
rx="5"
|
|
406
|
+
x="5"
|
|
407
|
+
y="6"
|
|
408
|
+
class="help-info"
|
|
409
|
+
onClick={toggleInfo}
|
|
410
|
+
/>
|
|
411
|
+
<text y="6" class="help-info-text">
|
|
412
|
+
<tspan x="10" dy="10">
|
|
413
|
+
This histogram shows the frequency of unique 'shot' results.
|
|
414
|
+
</tspan>
|
|
415
|
+
<tspan x="10" dy="10">
|
|
416
|
+
Click the top-left 'settings' icon for display options.
|
|
417
|
+
</tspan>
|
|
418
|
+
<tspan x="10" dy="10">
|
|
419
|
+
You can zoom the chart using the mouse scroll wheel.
|
|
420
|
+
</tspan>
|
|
421
|
+
<tspan x="10" dy="7">
|
|
422
|
+
(Or using a trackpad gesture).
|
|
423
|
+
</tspan>
|
|
424
|
+
<tspan x="10" dy="10">
|
|
425
|
+
When zoomed, to pan left & right, press 'Alt' while scrolling.
|
|
426
|
+
</tspan>
|
|
427
|
+
<tspan x="10" dy="10">
|
|
428
|
+
Click on a bar to filter the shot details to that result.
|
|
429
|
+
</tspan>
|
|
430
|
+
<tspan x="10" dy="12">
|
|
431
|
+
Click anywhere in this box to dismiss it.
|
|
432
|
+
</tspan>
|
|
433
|
+
</text>
|
|
434
|
+
</g>
|
|
435
|
+
</svg>
|
|
436
|
+
</>
|
|
437
|
+
);
|
|
438
|
+
}
|
package/ux/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
// By importing the CSS here, esbuild will by default bundle it up and copy it
|
|
5
|
+
// to a CSS file adjacent to the JS bundle and with the same name.
|
|
6
|
+
import "./qsharp-ux.css";
|
|
7
|
+
|
|
8
|
+
export { Histogram } from "./histogram.js";
|
|
9
|
+
export { ReTable, type ReData } from "./reTable.js";
|
|
10
|
+
export { SpaceChart } from "./spaceChart.js";
|
|
11
|
+
export { ResultsTable, type CellValue } from "./resultsTable.js";
|