darabos commited on
Commit
f78e803
·
2 Parent(s): b4180d6 351ac4e

Merge pull request #135 from biggraph/darabos-monaco

Browse files
examples/matplotlib_example.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # From https://matplotlib.org/stable/gallery/images_contours_and_fields/contour_corner_mask.html
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ from lynxkite.core.ops import op
5
+
6
+
7
+ @op("LynxKite Graph Analytics", "Matplotlib example", view="matplotlib")
8
+ def example():
9
+ # Data to plot.
10
+ x, y = np.meshgrid(np.arange(7), np.arange(10))
11
+ z = np.sin(0.5 * x) * np.cos(0.52 * y)
12
+
13
+ # Mask various z values.
14
+ mask = np.zeros_like(z, dtype=bool)
15
+ mask[2, 3:5] = True
16
+ mask[3:5, 4] = True
17
+ mask[7, 2] = True
18
+ mask[5, 0] = True
19
+ mask[0, 6] = True
20
+ z = np.ma.array(z, mask=mask)
21
+ print(z)
22
+
23
+ corner_masks = [False, True]
24
+ fig, axs = plt.subplots(ncols=2)
25
+ for ax, corner_mask in zip(axs, corner_masks):
26
+ cs = ax.contourf(x, y, z, corner_mask=corner_mask)
27
+ ax.contour(cs, colors="k")
28
+ ax.set_title(f"{corner_mask=}")
29
+
30
+ # Plot grid.
31
+ ax.grid(c="k", ls="-", alpha=0.3)
32
+
33
+ # Indicate masked points with red circles.
34
+ ax.plot(np.ma.array(x, mask=~mask), y, "ro")
examples/requirements.txt CHANGED
@@ -1,2 +1,3 @@
1
  # Example of a requirements.txt file. LynxKite will automatically install anything you put here.
2
  faker
 
 
1
  # Example of a requirements.txt file. LynxKite will automatically install anything you put here.
2
  faker
3
+ matplotlib
lynxkite-app/web/src/Code.tsx CHANGED
@@ -3,7 +3,7 @@
3
  import Editor, { type Monaco } from "@monaco-editor/react";
4
  import type { editor } from "monaco-editor";
5
  import { useEffect, useRef } from "react";
6
- import { useParams } from "react-router";
7
  import { WebsocketProvider } from "y-websocket";
8
  import * as Y from "yjs";
9
  // @ts-ignore
@@ -22,9 +22,12 @@ export default function Code() {
22
  const wsProviderRef = useRef<any>();
23
  const monacoBindingRef = useRef<any>();
24
  const yMonacoRef = useRef<any>();
 
25
  const editorRef = useRef<any>();
26
  useEffect(() => {
27
  const loadMonaco = async () => {
 
 
28
  // y-monaco is gigantic. The other Monaco packages are small.
29
  yMonacoRef.current = await import("y-monaco");
30
  initCRDT();
@@ -34,7 +37,9 @@ export default function Code() {
34
  function beforeMount(monaco: Monaco) {
35
  monaco.editor.defineTheme("lynxkite", theme);
36
  }
37
- function onMount(_editor: editor.IStandaloneCodeEditor) {
 
 
38
  editorRef.current = _editor;
39
  initCRDT();
40
  }
@@ -66,9 +71,9 @@ export default function Code() {
66
  return (
67
  <div className="workspace">
68
  <div className="top-bar bg-neutral">
69
- <a className="logo" href="">
70
  <img alt="" src={favicon} />
71
- </a>
72
  <div className="ws-name">{path}</div>
73
  <div className="tools text-secondary">
74
  <button className="btn btn-link">
@@ -77,9 +82,9 @@ export default function Code() {
77
  <button className="btn btn-link">
78
  <Backspace />
79
  </button>
80
- <a href={`/dir/${parentDir}`} className="btn btn-link">
81
  <Close />
82
- </a>
83
  </div>
84
  </div>
85
  <Editor
 
3
  import Editor, { type Monaco } from "@monaco-editor/react";
4
  import type { editor } from "monaco-editor";
5
  import { useEffect, useRef } from "react";
6
+ import { Link, useParams } from "react-router";
7
  import { WebsocketProvider } from "y-websocket";
8
  import * as Y from "yjs";
9
  // @ts-ignore
 
22
  const wsProviderRef = useRef<any>();
23
  const monacoBindingRef = useRef<any>();
24
  const yMonacoRef = useRef<any>();
25
+ const yMonacoLoadingRef = useRef(false);
26
  const editorRef = useRef<any>();
27
  useEffect(() => {
28
  const loadMonaco = async () => {
29
+ if (yMonacoLoadingRef.current) return;
30
+ yMonacoLoadingRef.current = true;
31
  // y-monaco is gigantic. The other Monaco packages are small.
32
  yMonacoRef.current = await import("y-monaco");
33
  initCRDT();
 
37
  function beforeMount(monaco: Monaco) {
38
  monaco.editor.defineTheme("lynxkite", theme);
39
  }
40
+ function onMount(_editor: editor.IStandaloneCodeEditor, monaco: Monaco) {
41
+ // Do nothing on Ctrl+S. We save after every keypress anyway.
42
+ _editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {});
43
  editorRef.current = _editor;
44
  initCRDT();
45
  }
 
71
  return (
72
  <div className="workspace">
73
  <div className="top-bar bg-neutral">
74
+ <Link className="logo" to="">
75
  <img alt="" src={favicon} />
76
+ </Link>
77
  <div className="ws-name">{path}</div>
78
  <div className="tools text-secondary">
79
  <button className="btn btn-link">
 
82
  <button className="btn btn-link">
83
  <Backspace />
84
  </button>
85
+ <Link to={`/dir/${parentDir}`} className="btn btn-link">
86
  <Close />
87
+ </Link>
88
  </div>
89
  </div>
90
  <Editor
lynxkite-core/src/lynxkite/core/ops.py CHANGED
@@ -222,13 +222,17 @@ def op(env: str, name: str, *, view="basic", outputs=None, params=None, slow=Fal
222
  _outputs = {name: Output(name=name, type=None) for name in outputs}
223
  else:
224
  _outputs = {"output": Output(name="output", type=None)} if view == "basic" else {}
 
 
 
 
225
  op = Op(
226
  func=func,
227
  name=name,
228
  params=_params,
229
  inputs=inputs,
230
  outputs=_outputs,
231
- type=view,
232
  )
233
  CATALOGS.setdefault(env, {})
234
  CATALOGS[env][name] = op
@@ -238,6 +242,24 @@ def op(env: str, name: str, *, view="basic", outputs=None, params=None, slow=Fal
238
  return decorator
239
 
240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  def input_position(**kwargs):
242
  """Decorator for specifying unusual positions for the inputs."""
243
 
 
222
  _outputs = {name: Output(name=name, type=None) for name in outputs}
223
  else:
224
  _outputs = {"output": Output(name="output", type=None)} if view == "basic" else {}
225
+ _view = view
226
+ if view == "matplotlib":
227
+ _view = "image"
228
+ func = matplotlib_to_image(func)
229
  op = Op(
230
  func=func,
231
  name=name,
232
  params=_params,
233
  inputs=inputs,
234
  outputs=_outputs,
235
+ type=_view,
236
  )
237
  CATALOGS.setdefault(env, {})
238
  CATALOGS[env][name] = op
 
242
  return decorator
243
 
244
 
245
+ def matplotlib_to_image(func):
246
+ import matplotlib.pyplot as plt
247
+ import base64
248
+ import io
249
+
250
+ @functools.wraps(func)
251
+ def wrapper(*args, **kwargs):
252
+ func(*args, **kwargs)
253
+ buf = io.BytesIO()
254
+ plt.savefig(buf, format="png")
255
+ plt.close()
256
+ buf.seek(0)
257
+ image_base64 = base64.b64encode(buf.read()).decode("utf-8")
258
+ return f"data:image/png;base64,{image_base64}"
259
+
260
+ return wrapper
261
+
262
+
263
  def input_position(**kwargs):
264
  """Decorator for specifying unusual positions for the inputs."""
265