qsharp-lang 1.0.34-dev → 1.1.0-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.
@@ -1,7 +1,8 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
 
4
- import { CreateIntegerTicks, CreateTimeTicks, Tick } from "../src/ux/ticks.js";
4
+ import { useRef, useEffect } from "preact/hooks";
5
+ import * as utils from "../src/utils.js";
5
6
 
6
7
  export type ScatterSeries = {
7
8
  color: string;
@@ -24,159 +25,48 @@ type Range = {
24
25
  max: number;
25
26
  };
26
27
 
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
28
  export function ScatterChart(props: {
108
29
  data: ScatterSeries[];
109
30
  xAxis: Axis;
110
31
  yAxis: Axis;
111
32
  onPointSelected(seriesIndex: number, pointIndex: number): void;
33
+ selectedPoint?: [number, number];
112
34
  }) {
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);
35
+ const selectedTooltipDiv = useRef<HTMLDivElement>(null);
139
36
 
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
- };
37
+ const { rangeX, rangeY } = utils.getRanges(props.data, 2 /* coefficient */);
149
38
 
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
- }
39
+ function createAxisTicks(range: Range, isTime: boolean): utils.Tick[] {
40
+ return isTime
41
+ ? utils.CreateTimeTicks(range.min, range.max)
42
+ : utils.CreateIntegerTicks(range.min, range.max);
156
43
  }
157
44
 
158
45
  const xTicks = createAxisTicks(rangeX, props.xAxis.isTime);
159
46
  const yTicks = createAxisTicks(rangeY, props.yAxis.isTime);
160
47
 
161
- function coordinateToSvgLogarithmic(
162
- value: number,
163
- range: Range,
164
- size: number,
165
- ): number {
48
+ function coordinateToLogarithmic(value: number, range: Range): number {
166
49
  return (
167
- ((Math.log(value) - Math.log(range.min)) /
168
- (Math.log(range.max) - Math.log(range.min))) *
169
- size
50
+ (Math.log(value) - Math.log(range.min)) /
51
+ (Math.log(range.max) - Math.log(range.min))
170
52
  );
171
53
  }
172
54
 
55
+ function toLogX(val: number): number {
56
+ return coordinateToLogarithmic(val, rangeX) * plotAreaWidth;
57
+ }
58
+
59
+ function toLogY(val: number): number {
60
+ return -coordinateToLogarithmic(val, rangeY) * plotAreaHeight;
61
+ }
62
+
173
63
  const yAxisTitleWidth = 20;
174
64
  const yAxisTickCaptionMaxWidth = 100;
175
65
  const axisTickLength = 5;
176
66
  const axisLineWidth = 1;
177
67
  const xLeftMargin =
178
68
  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.
69
+ const xRightMargin = 160; // to show tooltips on the right hand side. If we can move tooltips dynamically, we can get rid of this.
180
70
 
181
71
  const axisTitleHeight = 20;
182
72
  const xAxisTickCaptionMaxHeight = 16;
@@ -195,175 +85,216 @@ export function ScatterChart(props: {
195
85
  const plotAreaWidth = svgWidth - xLeftMargin - xRightMargin;
196
86
  const plotAreaHeight = svgHeight - yMargin;
197
87
 
198
- const viewBox = `${svgXMin - svgViewBoxWidthPadding} ${
199
- -plotAreaHeight - svgViewBoxHeightPadding
200
- } ${svgWidth + svgViewBoxWidthPadding} ${
201
- svgHeight + svgViewBoxHeightPadding
202
- }`;
88
+ const viewBox = `${svgXMin} ${-plotAreaHeight - svgViewBoxHeightPadding} ${
89
+ svgWidth + svgViewBoxWidthPadding
90
+ } ${svgHeight + svgViewBoxHeightPadding}`;
203
91
 
204
92
  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
- />
93
+ const yAxisTextYPadding = 4;
94
+
95
+ function renderTooltip(
96
+ topDiv: HTMLDivElement,
97
+ point: SVGCircleElement,
98
+ tooltip: HTMLDivElement,
99
+ ) {
100
+ const label = point.getAttribute("data-label");
101
+ tooltip.textContent = label;
102
+ const halfWidth = tooltip.offsetWidth / 2;
103
+ const pointRect = point.getBoundingClientRect();
104
+ const centerY = (pointRect.top + pointRect.bottom) / 2;
105
+ const centerX = (pointRect.left + pointRect.right) / 2;
106
+ const divRect = topDiv.getBoundingClientRect();
107
+ tooltip.style.left = `${centerX - divRect.left - halfWidth}px`;
108
+ tooltip.style.top = `${centerY - divRect.top + 12}px`;
109
+ tooltip.style.visibility = "visible";
110
+ }
260
111
 
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
- })}
112
+ function onPointMouseEvent(ev: MouseEvent, eventType: string) {
113
+ // Ensure we have a point as the target
114
+ if (!(ev.target instanceof SVGCircleElement)) return;
115
+ const target = ev.target as SVGCircleElement;
116
+ if (!target.classList.contains("qs-scatterChart-point")) return;
117
+
118
+ // Get the div enclosing the chart, and the popup child of it.
119
+ const topDiv = target.closest("div") as HTMLDivElement;
120
+ const popup = topDiv.querySelector(
121
+ ".qs-scatterChart-tooltip",
122
+ ) as HTMLDivElement;
123
+
124
+ switch (eventType) {
125
+ case "over":
126
+ {
127
+ renderTooltip(topDiv, target, popup);
128
+ }
129
+ break;
130
+ case "out":
131
+ popup.style.visibility = "hidden";
132
+ break;
133
+ case "click":
134
+ {
135
+ if (target.classList.contains("qs-scatterChart-point-selected")) {
136
+ // Clicked on the already selected point, so delete the point/row
137
+ props.onPointSelected(-1, 0);
138
+ } else {
139
+ const index = JSON.parse(target.getAttribute("data-index")!);
140
+ props.onPointSelected(index[0], index[1]);
141
+ }
142
+ }
143
+ break;
144
+ default:
145
+ console.error("Unknown event type: ", eventType);
146
+ }
147
+ }
287
148
 
288
- <text
289
- x={plotAreaWidth / 2}
290
- y={yMargin}
291
- class="qs-scatterChart-x-axisTitle"
292
- >
293
- {props.xAxis.label} (logarithmic)
294
- </text>
149
+ function getSelectedPointData() {
150
+ if (!props.selectedPoint) return null;
151
+ const series = props.data[props.selectedPoint[0]];
152
+ const item = series.items[props.selectedPoint[1]];
153
+ return { ...item, color: series.color };
154
+ }
155
+ const selectedPoint = getSelectedPointData();
295
156
 
296
- <text
297
- x={xLeftMargin - axisTitleHeight}
298
- y={plotAreaHeight / 2}
299
- class="qs-scatterChart-y-axisTitle"
300
- >
301
- {props.yAxis.label} (logarithmic)
302
- </text>
157
+ // Need to render first to get the element layout to position the tooltip
158
+ useEffect(() => {
159
+ if (!selectedTooltipDiv.current) return;
160
+ if (!props.selectedPoint) {
161
+ selectedTooltipDiv.current.style.visibility = "hidden";
162
+ } else {
163
+ // Locate the selected point and put the tooltip under it
164
+ const topDiv = selectedTooltipDiv.current.parentElement as HTMLDivElement;
165
+ const selectedPoint = topDiv?.querySelector(
166
+ ".qs-scatterChart-point-selected",
167
+ ) as SVGCircleElement;
168
+ if (!selectedPoint) return;
169
+ renderTooltip(topDiv, selectedPoint, selectedTooltipDiv.current);
170
+ }
171
+ });
303
172
 
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>
173
+ // The mouse events (over, out, and click) bubble, so put the hanlders on the
174
+ // SVG element and check the target element in the handler.
175
+ return (
176
+ <div style="position: relative">
177
+ <svg
178
+ style="margin-top: 12px"
179
+ viewBox={viewBox}
180
+ width={svgWidth}
181
+ height={svgHeight}
182
+ onMouseOver={(ev) => onPointMouseEvent(ev, "over")}
183
+ onMouseOut={(ev) => onPointMouseEvent(ev, "out")}
184
+ onClick={(ev) => onPointMouseEvent(ev, "click")}
185
+ >
186
+ <line
187
+ class="qs-scatterChart-axis"
188
+ x1="0"
189
+ y1="0"
190
+ x2={plotAreaWidth}
191
+ y2="0"
192
+ />
193
+
194
+ {xTicks.map((tick) => {
195
+ return (
196
+ <>
197
+ <line
198
+ y1="1"
199
+ y2={axisTickLength}
200
+ x1={toLogX(tick.value)}
201
+ x2={toLogX(tick.value)}
202
+ class="qs-scatterChart-tick-line"
203
+ />
204
+ <text
205
+ y={axisTickLength + xAxisTickCaptionMaxHeight}
206
+ x={toLogX(tick.value)}
207
+ class="qs-scatterChart-x-tick-text"
208
+ >
209
+ {tick.label}
210
+ </text>
211
+ </>
212
+ );
213
+ })}
214
+
215
+ <line
216
+ class="qs-scatterChart-axis"
217
+ x1="0"
218
+ y1="0"
219
+ x2="0"
220
+ y2={-svgHeight}
221
+ />
222
+
223
+ {yTicks.map((tick) => {
224
+ return (
225
+ <>
226
+ <line
227
+ x1="0"
228
+ x2={-axisTickLength}
229
+ y1={toLogY(tick.value)}
230
+ y2={toLogY(tick.value)}
231
+ class="qs-scatterChart-tick-line"
232
+ />
233
+ <text
234
+ x={-axisTickLength - yAxisTextPaddingFromTicks}
235
+ y={toLogY(tick.value) + yAxisTextYPadding}
236
+ class="qs-scatterChart-y-tick-text"
237
+ >
238
+ {tick.label}
239
+ </text>
240
+ </>
241
+ );
242
+ })}
243
+
244
+ <text
245
+ x={plotAreaWidth / 2}
246
+ y={yMargin}
247
+ class="qs-scatterChart-x-axisTitle"
248
+ >
249
+ {props.xAxis.label} (logarithmic)
250
+ </text>
311
251
 
312
- {data.map((data, seriesIndex) => {
313
- return data.items.map((item, pointIndex) => {
314
- const x = coordinateToSvgLogarithmic(
315
- item.x,
316
- rangeX,
317
- plotAreaWidth,
318
- );
252
+ <text
253
+ x={xLeftMargin - axisTitleHeight}
254
+ y={plotAreaHeight / 2}
255
+ class="qs-scatterChart-y-axisTitle"
256
+ >
257
+ {props.yAxis.label} (logarithmic)
258
+ </text>
319
259
 
320
- const y = -coordinateToSvgLogarithmic(
321
- item.y,
322
- rangeY,
323
- plotAreaHeight,
324
- );
260
+ <text
261
+ class="qs-scatterChart-watermark"
262
+ x={xLeftMargin - axisTitleHeight}
263
+ y={-svgHeight + yMargin}
264
+ >
265
+ Created with Azure Quantum Resource Estimator
266
+ </text>
267
+ <g>
268
+ {props.data.map((series, seriesIdx) => {
269
+ return series.items.map((plot, plotIdx) => {
325
270
  return (
326
271
  <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()}
272
+ data-index={JSON.stringify([seriesIdx, plotIdx])}
273
+ data-label={plot.label}
274
+ class="qs-scatterChart-point qs-scatterChart-hover"
275
+ cx={toLogX(plot.x)}
276
+ cy={toLogY(plot.y)}
277
+ stroke={series.color}
342
278
  />
343
279
  );
344
280
  });
345
281
  })}
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"
282
+ </g>
283
+ {
284
+ // Render the selected point last, so it's always on top of the others
285
+ selectedPoint ? (
286
+ <circle
287
+ class="qs-scatterChart-point qs-scatterChart-point-selected"
288
+ data-label={selectedPoint.label}
289
+ cx={toLogX(selectedPoint.x)}
290
+ cy={toLogY(selectedPoint.y)}
291
+ stroke={selectedPoint.color}
356
292
  />
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>
293
+ ) : null
294
+ }
295
+ </svg>
296
+ <div class="qs-scatterChart-selectedInfo" ref={selectedTooltipDiv}></div>
297
+ <div class="qs-scatterChart-tooltip"></div>
367
298
  </div>
368
299
  );
369
300
  }
package/ux/tsconfig.json CHANGED
@@ -4,7 +4,6 @@
4
4
  "target": "ES2020",
5
5
  "noEmit": true,
6
6
  "lib": ["DOM", "ES2020"],
7
- "rootDir": ".",
8
7
  "strict": true /* enable all strict type-checking options */,
9
8
  "jsx": "react-jsx",
10
9
  "jsxImportSource": "preact",
@@ -1,6 +0,0 @@
1
- export type Tick = {
2
- value: number;
3
- label: string;
4
- };
5
- export declare function CreateIntegerTicks(min: number, max: number): Tick[];
6
- export declare function CreateTimeTicks(min: number, max: number): Tick[];
@@ -1,19 +0,0 @@
1
- import { type VSDiagnostic } from "../lib/web/qsc_wasm.js";
2
- export { type VSDiagnostic } from "../lib/web/qsc_wasm.js";
3
- /**
4
- * @param positions - An array of utf-8 code unit indexes to map to utf-16 code unit indexes
5
- * @param source - The source code to do the mapping on
6
- * @returns An object where the keys are the utf-8 index and the values are the utf-16 index
7
- */
8
- export declare function mapUtf16UnitsToUtf8Units(positions: Array<number>, source: string): {
9
- [index: number]: number;
10
- };
11
- /**
12
- * @param positions - An array of utf-8 code unit indexes to map to utf-16 code unit indexes
13
- * @param source - The source code to do the mapping on
14
- * @returns An object where the keys are the utf-8 index and the values are the utf-16 index
15
- */
16
- export declare function mapUtf8UnitsToUtf16Units(positions: Array<number>, source: string): {
17
- [index: number]: number;
18
- };
19
- export declare function mapDiagnostics(diags: VSDiagnostic[], code: string): VSDiagnostic[];