qsharp-lang 1.0.32-dev → 1.0.34-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.
@@ -2,44 +2,48 @@
2
2
  // Licensed under the MIT License.
3
3
 
4
4
  import { useRef, useState } from "preact/hooks";
5
- import { type ReData } from "./reTable.js";
6
5
 
7
- type CellValue = string | number | { value: string; sortBy: number };
6
+ export type CellValue = string | number | { value: string; sortBy: number };
7
+ export type Row = {
8
+ color: string;
9
+ cells: CellValue[];
10
+ };
8
11
 
9
12
  // Note: column 0 is expected to be unique amongst all rows
10
13
  export function ResultsTable(props: {
11
14
  columnNames: string[];
12
- data: ReData[];
15
+ rows: Row[];
13
16
  initialColumns: number[];
14
17
  ensureSelected: boolean;
15
- onRowSelected(rowId: string): void;
16
18
  onRowDeleted(rowId: string): void;
19
+ selectedRow: string | null; // type selected to confirm with the useState pattern on the parent component
20
+ setSelectedRow(rowId: string): void;
17
21
  }) {
18
22
  const [showColumns, setShowColumns] = useState(props.initialColumns);
19
23
  const [sortColumn, setSortColumn] = useState<{
20
24
  columnId: number;
21
25
  ascending: boolean;
22
26
  } | null>(null);
23
- const [selectedRow, setSelectedRow] = useState<string>("");
27
+
24
28
  const [showColumnMenu, setShowColumnMenu] = useState(false);
25
29
  const [showRowMenu, setShowRowMenu] = useState("");
26
30
 
27
- const rows = props.data.map(ReDataToRow);
28
-
29
31
  // Find the first row that is new in the current sort order
30
- const newest = getSortedRows(rows).find(
31
- (row) => (row[row.length - 1] as string) === "New",
32
+ const newest = getSortedRows(props.rows).find(
33
+ (row) => (row.cells[row.cells.length - 1] as string) === "New",
32
34
  );
33
35
 
34
36
  // Select the first of the newest rows, otherwise preserve the existing selection
35
37
  if (newest && props.ensureSelected) {
36
- const rowId = newest[0].toString();
38
+ const rowId = newest.cells[0].toString();
37
39
  setSelectedRow(rowId);
38
- props.onRowSelected(rowId);
39
- } else if (!selectedRow && props.ensureSelected && rows.length > 0) {
40
- const rowId = rows[0][0].toString();
40
+ } else if (
41
+ !props.selectedRow &&
42
+ props.ensureSelected &&
43
+ props.rows.length > 0
44
+ ) {
45
+ const rowId = props.rows[0].cells[0].toString();
41
46
  setSelectedRow(rowId);
42
- props.onRowSelected(rowId);
43
47
  }
44
48
 
45
49
  // Use to track the column being dragged
@@ -79,6 +83,10 @@ export function ResultsTable(props: {
79
83
  }
80
84
  }
81
85
 
86
+ function setSelectedRow(rowId: string) {
87
+ props.setSelectedRow(rowId);
88
+ }
89
+
82
90
  function onDragOver(ev: DragEvent) {
83
91
  if (!(ev.target instanceof HTMLElement)) return;
84
92
  const thisColId = ev.target.closest("th")?.dataset["colid"];
@@ -156,7 +164,7 @@ export function ResultsTable(props: {
156
164
  }
157
165
  }
158
166
 
159
- function getSortedRows(rows: CellValue[][]) {
167
+ function getSortedRows(rows: Row[]) {
160
168
  if (!sortColumn) return rows;
161
169
 
162
170
  const colIdx = sortColumn.columnId;
@@ -164,8 +172,8 @@ export function ResultsTable(props: {
164
172
 
165
173
  const sortedRows = [...rows];
166
174
  sortedRows.sort((a, b) => {
167
- const aVal = a[colIdx];
168
- const bVal = b[colIdx];
175
+ const aVal = a.cells[colIdx];
176
+ const bVal = b.cells[colIdx];
169
177
  if (typeof aVal === "string" && typeof bVal === "string") {
170
178
  return ascending ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
171
179
  } else if (typeof aVal === "number" && typeof bVal === "number") {
@@ -193,11 +201,10 @@ export function ResultsTable(props: {
193
201
  }
194
202
 
195
203
  function rowClicked(rowId: string) {
196
- if (selectedRow === rowId && props.ensureSelected) return;
204
+ if (props.selectedRow === rowId && props.ensureSelected) return;
197
205
 
198
- const newSelectedRow = selectedRow === rowId ? "" : rowId;
206
+ const newSelectedRow = props.selectedRow === rowId ? "" : rowId;
199
207
  setSelectedRow(newSelectedRow);
200
- props.onRowSelected(newSelectedRow);
201
208
  }
202
209
 
203
210
  function onClickRowMenu(ev: MouseEvent, rowid: string) {
@@ -246,9 +253,8 @@ export function ResultsTable(props: {
246
253
  e.stopPropagation();
247
254
  // Clear out any menus or selections for the row if needed
248
255
  setShowRowMenu("");
249
- if (selectedRow === rowId) {
256
+ if (props.selectedRow === rowId) {
250
257
  setSelectedRow("");
251
- props.onRowSelected("");
252
258
  }
253
259
  props.onRowDeleted(rowId);
254
260
  }
@@ -344,14 +350,14 @@ export function ResultsTable(props: {
344
350
  </tr>
345
351
  </thead>
346
352
  <tbody>
347
- {getSortedRows(rows).map((row) => {
348
- const rowId = row[0].toString();
353
+ {getSortedRows(props.rows).map((row) => {
354
+ const rowId = row.cells[0].toString();
349
355
  return (
350
356
  <tr
351
357
  onClick={() => rowClicked(rowId)}
352
358
  data-rowid={rowId}
353
359
  class={
354
- rowId === selectedRow
360
+ rowId === props.selectedRow
355
361
  ? "qs-resultsTable-sortedTableSelectedRow"
356
362
  : undefined
357
363
  }
@@ -364,7 +370,7 @@ export function ResultsTable(props: {
364
370
  <svg width="16" height="16" style="position: relative;">
365
371
  <path
366
372
  stroke-width="1.5"
367
- stroke="gray"
373
+ stroke={row.color}
368
374
  stroke-linecap="round"
369
375
  d="M4,5 h8 M4,8 h8 M4,11 h8"
370
376
  />
@@ -386,7 +392,9 @@ export function ResultsTable(props: {
386
392
  </td>
387
393
  {showColumns.map((idx) => {
388
394
  return (
389
- <td data-colid={idx.toString()}>{getCellStr(row[idx])}</td>
395
+ <td data-colid={idx.toString()}>
396
+ {getCellStr(row.cells[idx])}
397
+ </td>
390
398
  );
391
399
  })}
392
400
  </tr>
@@ -396,25 +404,3 @@ export function ResultsTable(props: {
396
404
  </table>
397
405
  );
398
406
  }
399
-
400
- function ReDataToRow(data: ReData): CellValue[] {
401
- return [
402
- data.jobParams.runName,
403
- data.jobParams.qubitParams.name,
404
- data.jobParams.qecScheme.name,
405
- data.jobParams.errorBudget,
406
- data.physicalCounts.breakdown.algorithmicLogicalQubits,
407
- data.physicalCounts.breakdown.algorithmicLogicalDepth,
408
- data.logicalQubit.codeDistance,
409
- data.physicalCounts.breakdown.numTstates,
410
- data.physicalCounts.breakdown.numTfactories,
411
- data.physicalCountsFormatted.physicalQubitsForTfactoriesPercentage,
412
- {
413
- value: data.physicalCountsFormatted.runtime,
414
- sortBy: data.physicalCounts.runtime,
415
- },
416
- data.physicalCounts.rqops,
417
- data.physicalCounts.physicalQubits,
418
- data.new ? "New" : "Cached",
419
- ];
420
- }
@@ -0,0 +1,369 @@
1
+ // Copyright (c) Microsoft Corporation.
2
+ // Licensed under the MIT License.
3
+
4
+ import { CreateIntegerTicks, CreateTimeTicks, Tick } from "../src/ux/ticks.js";
5
+
6
+ export type ScatterSeries = {
7
+ color: string;
8
+ items: PlotItem[];
9
+ };
10
+
11
+ export type PlotItem = {
12
+ x: number;
13
+ y: number;
14
+ label: string;
15
+ };
16
+
17
+ export type Axis = {
18
+ isTime: boolean;
19
+ label: string;
20
+ };
21
+
22
+ type Range = {
23
+ min: number;
24
+ max: number;
25
+ };
26
+
27
+ export function HideTooltip() {
28
+ const tooltip = document.getElementById("tooltip");
29
+ if (tooltip) {
30
+ tooltip.setAttribute("visibility", "hidden");
31
+ }
32
+ }
33
+
34
+ function drawTooltip(target: SVGCircleElement, clicked: boolean = false) {
35
+ const xAttr = target.getAttribute("cx");
36
+ const x = xAttr ? parseInt(xAttr) : 0;
37
+ const yAttr = target.getAttribute("cy");
38
+ const y = yAttr ? parseInt(yAttr) : -0;
39
+ const text = target.getAttribute("data-label");
40
+ const tooltipTextLeftPadding = 5;
41
+ const tooltipRectanglePaddingHeight = 10;
42
+ const tooltipTextPaddingHeight = 25;
43
+ const tooltip = document.getElementById("tooltip");
44
+ const tooltipRect = document.getElementById("tooltipRect");
45
+ const tooltipText = document.getElementById(
46
+ "tooltipText",
47
+ ) as unknown as SVGTextElement;
48
+
49
+ if (tooltipText) {
50
+ tooltipText.setAttribute("x", (x + tooltipTextLeftPadding).toString());
51
+ tooltipText.setAttribute("y", (y + tooltipTextPaddingHeight).toString());
52
+ tooltipText.textContent = text;
53
+ }
54
+ if (tooltipRect && tooltipText) {
55
+ const box = tooltipText.getBBox();
56
+ const textWidth = box.width;
57
+ tooltipRect.setAttribute(
58
+ "width",
59
+ (textWidth + 2 * tooltipTextLeftPadding).toString(),
60
+ );
61
+ tooltipRect.setAttribute("x", x.toString());
62
+ tooltipRect.setAttribute(
63
+ "y",
64
+ (y + tooltipRectanglePaddingHeight).toString(),
65
+ );
66
+ }
67
+ if (tooltip) {
68
+ tooltip.setAttribute("visibility", "visible");
69
+ tooltip.setAttribute("clicked", clicked.toString());
70
+ }
71
+ }
72
+
73
+ function hideTooltipIfNotClicked() {
74
+ const tooltip = document.getElementById("tooltip");
75
+ if (tooltip) {
76
+ if (tooltip.getAttribute("clicked") === "false") {
77
+ tooltip.setAttribute("visibility", "hidden");
78
+ }
79
+ }
80
+ }
81
+
82
+ function deselectPoint() {
83
+ const chart = document.getElementById(`scatterChart`);
84
+ if (chart) {
85
+ if (chart.getAttribute("selectedPoint")) {
86
+ const point = document.getElementById(
87
+ chart.getAttribute("selectedPoint") as string,
88
+ );
89
+ if (point) {
90
+ point.classList.remove("qs-scatterChart-point-selected");
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ export function SelectPoint(seriesIndex: number, pointIndex: number) {
97
+ deselectPoint();
98
+ const point = document.getElementById(`point-${seriesIndex}-${pointIndex}`);
99
+ const chart = document.getElementById(`scatterChart`);
100
+ if (point && chart) {
101
+ point.classList.add("qs-scatterChart-point-selected");
102
+ chart.setAttribute("selectedPoint", point.id);
103
+ drawTooltip(point as unknown as SVGCircleElement, true);
104
+ }
105
+ }
106
+
107
+ export function ScatterChart(props: {
108
+ data: ScatterSeries[];
109
+ xAxis: Axis;
110
+ yAxis: Axis;
111
+ onPointSelected(seriesIndex: number, pointIndex: number): void;
112
+ }) {
113
+ const data = props.data;
114
+
115
+ function findMinMaxSingle(
116
+ series: ScatterSeries,
117
+ ): [number, number, number, number] {
118
+ const xs = series.items.map((item) => item.x);
119
+ const ys = series.items.map((item) => item.y);
120
+ const minX = Math.min(...xs);
121
+ const maxX = Math.max(...xs);
122
+ const minY = Math.min(...ys);
123
+ const maxY = Math.max(...ys);
124
+ return [minX, maxX, minY, maxY];
125
+ }
126
+
127
+ function findMinMaxAll(
128
+ series: ScatterSeries[],
129
+ ): [number, number, number, number] {
130
+ const minMax = series.map(findMinMaxSingle);
131
+ const minX = Math.min(...minMax.map((x) => x[0]));
132
+ const maxX = Math.max(...minMax.map((x) => x[1]));
133
+ const minY = Math.min(...minMax.map((x) => x[2]));
134
+ const maxY = Math.max(...minMax.map((x) => x[3]));
135
+ return [minX, maxX, minY, maxY];
136
+ }
137
+
138
+ const [minX, maxX, minY, maxY] = findMinMaxAll(data);
139
+
140
+ const rangeCoefficient = 2;
141
+ const rangeX: Range = {
142
+ min: minX / rangeCoefficient,
143
+ max: maxX * rangeCoefficient,
144
+ };
145
+ const rangeY: Range = {
146
+ min: minY / rangeCoefficient,
147
+ max: maxY * rangeCoefficient,
148
+ };
149
+
150
+ function createAxisTicks(range: Range, isTime: boolean): Tick[] {
151
+ if (isTime) {
152
+ return CreateTimeTicks(range.min, range.max);
153
+ } else {
154
+ return CreateIntegerTicks(range.min, range.max);
155
+ }
156
+ }
157
+
158
+ const xTicks = createAxisTicks(rangeX, props.xAxis.isTime);
159
+ const yTicks = createAxisTicks(rangeY, props.yAxis.isTime);
160
+
161
+ function coordinateToSvgLogarithmic(
162
+ value: number,
163
+ range: Range,
164
+ size: number,
165
+ ): number {
166
+ return (
167
+ ((Math.log(value) - Math.log(range.min)) /
168
+ (Math.log(range.max) - Math.log(range.min))) *
169
+ size
170
+ );
171
+ }
172
+
173
+ const yAxisTitleWidth = 20;
174
+ const yAxisTickCaptionMaxWidth = 100;
175
+ const axisTickLength = 5;
176
+ const axisLineWidth = 1;
177
+ const xLeftMargin =
178
+ yAxisTitleWidth + yAxisTickCaptionMaxWidth + axisTickLength + axisLineWidth;
179
+ const xRightMargin = 100; // to show tooltips on the right hand side. If we can move tooltips dynamicslly, we can get rid of this.
180
+
181
+ const axisTitleHeight = 20;
182
+ const xAxisTickCaptionMaxHeight = 16;
183
+ const yMargin =
184
+ axisTitleHeight +
185
+ xAxisTickCaptionMaxHeight +
186
+ axisTickLength +
187
+ axisLineWidth;
188
+
189
+ const svgWidth = 960;
190
+ const svgViewBoxWidthPadding = 50;
191
+ const svgHeight = 480;
192
+ const svgViewBoxHeightPadding = 10;
193
+ const svgXMin = -xLeftMargin;
194
+
195
+ const plotAreaWidth = svgWidth - xLeftMargin - xRightMargin;
196
+ const plotAreaHeight = svgHeight - yMargin;
197
+
198
+ const viewBox = `${svgXMin - svgViewBoxWidthPadding} ${
199
+ -plotAreaHeight - svgViewBoxHeightPadding
200
+ } ${svgWidth + svgViewBoxWidthPadding} ${
201
+ svgHeight + svgViewBoxHeightPadding
202
+ }`;
203
+
204
+ const yAxisTextPaddingFromTicks = 5;
205
+ const yAxisTextYPadding = 6;
206
+
207
+ return (
208
+ <div style="display: flex; flex-wrap: wrap; margin-top: 8px;">
209
+ <div class="chart-container">
210
+ <svg
211
+ width={svgWidth}
212
+ height={svgHeight}
213
+ viewBox={viewBox}
214
+ id="scatterChart"
215
+ >
216
+ <line
217
+ id="xAxis"
218
+ x1="0"
219
+ y1="0"
220
+ x2={plotAreaWidth}
221
+ y2="0"
222
+ stroke="var(--border-color)"
223
+ />
224
+
225
+ {xTicks.map((tick) => {
226
+ const x = coordinateToSvgLogarithmic(
227
+ tick.value,
228
+ rangeX,
229
+ plotAreaWidth,
230
+ );
231
+ return (
232
+ <g>
233
+ <line
234
+ x1={x}
235
+ y1="1"
236
+ x2={x}
237
+ y2={axisTickLength}
238
+ stroke="var(--border-color)"
239
+ />
240
+ <text
241
+ x={x}
242
+ y={axisTickLength + xAxisTickCaptionMaxHeight}
243
+ text-anchor="middle"
244
+ fill="var(--main-color)"
245
+ >
246
+ {tick.label}
247
+ </text>
248
+ </g>
249
+ );
250
+ })}
251
+
252
+ <line
253
+ id="yAxis"
254
+ x1="0"
255
+ y1="0"
256
+ x2="0"
257
+ y2={-svgHeight}
258
+ stroke="var(--border-color)"
259
+ />
260
+
261
+ {yTicks.map((tick) => {
262
+ const y = -coordinateToSvgLogarithmic(
263
+ tick.value,
264
+ rangeY,
265
+ plotAreaHeight,
266
+ );
267
+ return (
268
+ <g>
269
+ <line
270
+ x1="0"
271
+ y1={y}
272
+ x2={-axisTickLength}
273
+ y2={y}
274
+ stroke="var(--border-color)"
275
+ />
276
+ <text
277
+ x={-axisTickLength - yAxisTextPaddingFromTicks}
278
+ y={y + yAxisTextYPadding}
279
+ text-anchor="end"
280
+ fill="var(--main-color)"
281
+ >
282
+ {tick.label}
283
+ </text>
284
+ </g>
285
+ );
286
+ })}
287
+
288
+ <text
289
+ x={plotAreaWidth / 2}
290
+ y={yMargin}
291
+ class="qs-scatterChart-x-axisTitle"
292
+ >
293
+ {props.xAxis.label} (logarithmic)
294
+ </text>
295
+
296
+ <text
297
+ x={xLeftMargin - axisTitleHeight}
298
+ y={plotAreaHeight / 2}
299
+ class="qs-scatterChart-y-axisTitle"
300
+ >
301
+ {props.yAxis.label} (logarithmic)
302
+ </text>
303
+
304
+ <text
305
+ class="qs-scatterChart-watermark"
306
+ x={xLeftMargin - axisTitleHeight}
307
+ y={-svgHeight + yMargin}
308
+ >
309
+ Created with Azure Quantum Resource Estimator
310
+ </text>
311
+
312
+ {data.map((data, seriesIndex) => {
313
+ return data.items.map((item, pointIndex) => {
314
+ const x = coordinateToSvgLogarithmic(
315
+ item.x,
316
+ rangeX,
317
+ plotAreaWidth,
318
+ );
319
+
320
+ const y = -coordinateToSvgLogarithmic(
321
+ item.y,
322
+ rangeY,
323
+ plotAreaHeight,
324
+ );
325
+ return (
326
+ <circle
327
+ id={`point-${seriesIndex}-${pointIndex}`}
328
+ cx={x}
329
+ cy={y}
330
+ data-label={item.label}
331
+ class="qs-scatterChart-point"
332
+ stroke={data.color}
333
+ onMouseOver={(e) => {
334
+ drawTooltip(e.currentTarget, false);
335
+ deselectPoint();
336
+ }}
337
+ onClick={() => {
338
+ SelectPoint(seriesIndex, pointIndex);
339
+ props.onPointSelected(seriesIndex, pointIndex);
340
+ }}
341
+ onMouseOut={() => hideTooltipIfNotClicked()}
342
+ />
343
+ );
344
+ });
345
+ })}
346
+ <g id="tooltip" visibility="hidden">
347
+ <rect
348
+ id="tooltipRect"
349
+ x="100"
350
+ y="-100"
351
+ width="200"
352
+ height="22"
353
+ fill="white"
354
+ stroke="black"
355
+ stroke-width="1"
356
+ />
357
+ <text
358
+ id="tooltipText"
359
+ x="105"
360
+ y="115"
361
+ text-anchor="left"
362
+ fill="black"
363
+ ></text>
364
+ </g>
365
+ </svg>
366
+ </div>
367
+ </div>
368
+ );
369
+ }
package/ux/spaceChart.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
 
4
- import { ReData } from "./reTable.js";
4
+ import { SingleEstimateResult } from "./data.js";
5
5
 
6
6
  function getPieSegment(
7
7
  x: number,
@@ -26,7 +26,7 @@ function getPieSegment(
26
26
  return d;
27
27
  }
28
28
 
29
- export function SpaceChart(props: { estimatesData: ReData }) {
29
+ export function SpaceChart(props: { estimatesData: SingleEstimateResult }) {
30
30
  const breakdown = props.estimatesData.physicalCounts.breakdown;
31
31
 
32
32
  // The values to be shown on the pie chart