From 419f24229b5e699226a66ce62404acf7ebe24851 Mon Sep 17 00:00:00 2001 From: Sourabh Raj Jaiswal <37596585+srj31@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:49:43 +0800 Subject: [PATCH] Curves improve (#257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add curves * fix draw_sound_2d * 🚨 fix: linter errors * :bug: fix Sound import * fix merge issues * Revert "fix merge issues" This reverts commit 7b163bb15a6acbb592cc632a9c9a8ac4318f418c. * commit plotly changes --- src/bundles/plotly/curve_functions.ts | 14 ++- src/bundles/plotly/functions.ts | 148 ++++++++++++++++++++++++-- src/bundles/plotly/index.ts | 9 +- src/bundles/plotly/plotly.ts | 11 +- src/bundles/plotly/sound_functions.ts | 106 +++++------------- src/tabs/Plotly/index.tsx | 7 +- 6 files changed, 192 insertions(+), 103 deletions(-) diff --git a/src/bundles/plotly/curve_functions.ts b/src/bundles/plotly/curve_functions.ts index a19175205..e25fcc9ab 100644 --- a/src/bundles/plotly/curve_functions.ts +++ b/src/bundles/plotly/curve_functions.ts @@ -47,7 +47,7 @@ export function z_of(pt: Point): number { * ``` */ export function r_of(pt: Point): number { - return pt.color.r * 255; + return pt.color[0] ?? 0 * 255; } /** @@ -62,7 +62,7 @@ export function r_of(pt: Point): number { * ``` */ export function g_of(pt: Point): number { - return pt.color.g * 255; + return pt.color[1] ?? 0 * 255; } /** @@ -77,7 +77,7 @@ export function g_of(pt: Point): number { * ``` */ export function b_of(pt: Point): number { - return pt.color.b * 255; + return pt.color[2] ?? 0 * 255; } export function generatePlot( type: string, @@ -98,6 +98,7 @@ export function generatePlot( z_s.push(z_of(point)); color_s.push(`rgb(${r_of(point)},${g_of(point)},${b_of(point)})`); } + const plotlyData: Data = { x: x_s, y: y_s, @@ -106,6 +107,9 @@ export function generatePlot( size: 2, color: color_s, }, + line: { + color: color_s, + }, }; return new CurvePlot( draw_new_curve, @@ -119,5 +123,5 @@ export function generatePlot( } function draw_new_curve(divId: string, data: Data, layout: Partial) { - Plotly.newPlot(divId, [data], layout); -} + Plotly.react(divId, [data], layout); +} \ No newline at end of file diff --git a/src/bundles/plotly/functions.ts b/src/bundles/plotly/functions.ts index 7dc38bfeb..d1e5d1381 100644 --- a/src/bundles/plotly/functions.ts +++ b/src/bundles/plotly/functions.ts @@ -7,14 +7,17 @@ import context from 'js-slang/context'; import Plotly, { type Data, type Layout } from 'plotly.js-dist'; import { type Curve, - type CurvePlot, + CurvePlot, type CurvePlotFunction, DrawnPlot, type ListOfPairs, } from './plotly'; import { generatePlot } from './curve_functions'; +import { get_duration, get_wave, is_sound } from './sound_functions'; +import { type Sound } from '../sound/types'; + +let drawnPlots: (DrawnPlot | CurvePlot)[] = []; -const drawnPlots: (DrawnPlot | CurvePlot)[] = []; context.moduleContexts.plotly.state = { drawnPlots, }; @@ -121,7 +124,6 @@ export function new_plot(data: ListOfPairs): void { drawnPlots.push(new DrawnPlot(draw_new_plot, data)); } - /** * Adds a new plotly plot to the context which will be rendered in the Plotly Tabs * @example @@ -236,7 +238,6 @@ export function new_plot_json(data: any): void { drawnPlots.push(new DrawnPlot(draw_new_plot_json, data)); } - /** * @param data The data which plotly will use * @param divId The id of the div element on which the plot will be displayed @@ -309,19 +310,20 @@ function createPlotFunction( /** * Returns a function that turns a given Curve into a Drawing, by sampling the * Curve at `num` sample points and connecting each pair with a line. - * The parts between (0,0) and (1,1) of the resulting Drawing are shown in the window. * * @param num determines the number of points, lower than 65535, to be sampled. * Including 0 and 1, there are `num + 1` evenly spaced sample points * @return function of type Curve → Drawing * @example * ``` - * draw_connected(100)(t => make_point(t, t)); + * draw_connected_2d(100)(t => make_point(t, t)); * ``` */ export const draw_connected_2d = createPlotFunction( - 'scatter', - { mode: 'lines' }, + 'scattergl', + { + mode: 'lines', + }, { xaxis: { visible: false }, yaxis: { @@ -329,14 +331,138 @@ export const draw_connected_2d = createPlotFunction( scaleanchor: 'x', }, }, - + true, ); -export const draw_3D_points = createPlotFunction( +/** + * Returns a function that turns a given 3D Curve into a Drawing, by sampling the + * 3D Curve at `num` sample points and connecting each pair with a line. + * + * @param num determines the number of points, lower than 65535, to be sampled. + * Including 0 and 1, there are `num + 1` evenly spaced sample points + * @return function of type 3D Curve → Drawing + * @example + * ``` + * draw_connected_3d(100)(t => make_point(t, t)); + * ``` + */ +export const draw_connected_3d = createPlotFunction( 'scatter3d', + { mode: 'lines' }, + {}, + true, +); + +/** + * Returns a function that turns a given Curve into a Drawing, by sampling the + * Curve at num sample points. The Drawing consists of isolated points, and does not connect them. + * When a program evaluates to a Drawing, the Source system displays it graphically, in a window, + * + * * @param num determines the number of points, lower than 65535, to be sampled. + * Including 0 and 1, there are `num + 1` evenly spaced sample points + * @return function of type 2D Curve → Drawing + * @example + * ``` + * draw_points_2d(100)(t => make_point(t, t)); + */ +export const draw_points_2d = createPlotFunction( + 'scatter', { mode: 'markers' }, { - + xaxis: { visible: false }, + yaxis: { + visible: false, + scaleanchor: 'x', + }, }, true, ); + +/** + * Returns a function that turns a given 3D Curve into a Drawing, by sampling the + * 3D Curve at num sample points. The Drawing consists of isolated points, and does not connect them. + * When a program evaluates to a Drawing, the Source system displays it graphically, in a window, + * + * * @param num determines the number of points, lower than 65535, to be sampled. + * Including 0 and 1, there are `num + 1` evenly spaced sample points + * @return function of type 3D Curve → Drawing + * @example + * ``` + * draw_points_3d(100)(t => make_point(t, t)); + */ +export const draw_points_3d = createPlotFunction( + 'scatter3d', + { mode: 'markers' }, + {}, +); + +/** + * Visualizes the sound on a 2d line graph + * @param sound the sound which is to be visualized on plotly + */ +export const draw_sound_2d = (sound: Sound) => { + const FS: number = 44100; // Output sample rate + if (!is_sound(sound)) { + throw new Error( + `draw_sound_2d is expecting sound, but encountered ${sound}`, + ); + // If a sound is already displayed, terminate execution. + } else if (get_duration(sound) < 0) { + throw new Error('draw_sound_2d: duration of sound is negative'); + } else { + // Instantiate audio context if it has not been instantiated. + + // Create mono buffer + const channel: number[] = []; + const time_stamps: number[] = []; + const len = Math.ceil(FS * get_duration(sound)); + + const wave = get_wave(sound); + for (let i = 0; i < len; i += 1) { + time_stamps[i] = i / FS; + channel[i] = wave(i / FS); + } + + let x_s: number[] = []; + let y_s: number[] = []; + + for (let i = 0; i < channel.length; i += 1) { + x_s.push(time_stamps[i]); + y_s.push(channel[i]); + } + + const plotlyData: Data = { + x: x_s, + y: y_s, + }; + const plot = new CurvePlot( + draw_new_curve, + { + ...plotlyData, + type: 'scattergl', + mode: 'lines', + line: { width: 0.5 }, + } as Data, + { + xaxis: { + type: 'linear', + title: 'Time', + anchor: 'y', + position: 0, + rangeslider: { visible: true }, + }, + yaxis: { + type: 'linear', + visible: false, + }, + bargap: 0.2, + barmode: 'stack', + }, + ); + if (drawnPlots) drawnPlots.push(plot); + } +}; + +function draw_new_curve(divId: string, data: Data, layout: Partial) { + Plotly.react(divId, [data], layout); +} diff --git a/src/bundles/plotly/index.ts b/src/bundles/plotly/index.ts index 507ac9fa8..bb1ca049b 100644 --- a/src/bundles/plotly/index.ts +++ b/src/bundles/plotly/index.ts @@ -7,7 +7,8 @@ export { new_plot, new_plot_json, draw_connected_2d, - draw_3D_points, -} from './functions'; - -export { draw_sound_2d } from './sound_functions'; + draw_connected_3d, + draw_points_2d, + draw_points_3d, + draw_sound_2d, +} from './functions'; \ No newline at end of file diff --git a/src/bundles/plotly/plotly.ts b/src/bundles/plotly/plotly.ts index 5d3555ffc..3ad00f06b 100644 --- a/src/bundles/plotly/plotly.ts +++ b/src/bundles/plotly/plotly.ts @@ -1,5 +1,6 @@ import { type Data, type Layout } from 'plotly.js-dist'; import { type ReplResult } from '../../typings/type_helpers'; +import type { Pair } from 'js-slang/dist/stdlib/list'; /** * Represents plots with a draw method attached @@ -37,15 +38,14 @@ export class CurvePlot implements ReplResult { export type ListOfPairs = (ListOfPairs | any)[] | null; export type Data2d = number[]; -export type Color = { r: number, g: number, b: number }; +export type Color = { r: number; g: number; b: number }; export type DataTransformer = (c: Data2d[]) => Data2d[]; -export type CurvePlotFunction = ((func: Curve) => CurvePlot); +export type CurvePlotFunction = (func: Curve) => CurvePlot; -export type Curve = ((n: number) => Point); +export type Curve = (n: number) => Point; export type CurveTransformer = (c: Curve) => Curve; - /** Encapsulates 3D point with RGB values. */ export class Point implements ReplResult { constructor( @@ -57,3 +57,6 @@ export class Point implements ReplResult { public toReplString = () => `(${this.x}, ${this.y}, ${this.z}, Color: ${this.color})`; } + +export type Wave = (...t: any) => number; +export type Sound = Pair; \ No newline at end of file diff --git a/src/bundles/plotly/sound_functions.ts b/src/bundles/plotly/sound_functions.ts index e39500cfc..6d727eef4 100644 --- a/src/bundles/plotly/sound_functions.ts +++ b/src/bundles/plotly/sound_functions.ts @@ -1,81 +1,33 @@ -import context from 'js-slang/context'; -import Plotly, { type Data, type Layout } from 'plotly.js-dist'; -import { get_duration, get_wave, is_sound } from '../sound'; -import { type Sound } from '../sound/types'; -import { CurvePlot, type DrawnPlot } from './plotly'; - -const FS: number = 44100; // Output sample rate - -const drawnPlots: (DrawnPlot | CurvePlot)[] = []; -context.moduleContexts.plotly.state = { - drawnPlots, -}; - +import { + head, + tail, + is_pair, +} from 'js-slang/dist/stdlib/list'; +import { type Sound, type Wave } from '../sound/types'; +export function is_sound(x: any): x is Sound { + return ( + is_pair(x) + && typeof get_wave(x) === 'function' + && typeof get_duration(x) === 'number' + ); +} /** - * Visualizes the sound on a 2d line graph - * @param sound the sound which is to be visualized on plotly + * Accesses the wave function of a given Sound. + * + * @param sound given Sound + * @return the wave function of the Sound + * @example get_wave(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns t => Math_sin(2 * Math_PI * 440 * t) */ -export function draw_sound_2d(sound: Sound) { - if (!is_sound(sound)) { - throw new Error(`draw_sound_2d is expecting sound, but encountered ${sound}`); - // If a sound is already displayed, terminate execution. - } else if (get_duration(sound) < 0) { - throw new Error('draw_sound_2d: duration of sound is negative'); - } else { - // Instantiate audio context if it has not been instantiated. - - // Create mono buffer - const channel: number[] = []; - const time_stamps: number[] = []; - const len = Math.ceil(FS * get_duration(sound)); - - - const wave = get_wave(sound); - for (let i = 0; i < len; i += 1) { - time_stamps[i] = i / FS; - channel[i] = wave(i / FS); - } - - let x_s: number[] = []; - let y_s: number[] = []; - - for (let i = 0; i < channel.length; i += 1) { - x_s.push(time_stamps[i]); - y_s.push(channel[i]); - } - - const plotlyData: Data = { - x: x_s, - y: y_s, - }; - const plot = new CurvePlot( - draw_new_curve, - { - ...plotlyData, - type: 'scattergl', - mode: 'lines', - line: { width: 0.5 }, - } as Data, - { - xaxis: { - type: 'linear', - title: 'Time', - anchor: 'y', - position: 0, - rangeslider: { visible: true }, - }, - yaxis: { - type: 'linear', - visible: false, - }, - bargap: 0.2, - barmode: 'stack', - }, - ); - drawnPlots.push(plot); - } +export function get_wave(sound: Sound): Wave { + return head(sound); } - -function draw_new_curve(divId: string, data: Data, layout: Partial) { - Plotly.newPlot(divId, [data], layout); +/** + * Accesses the duration of a given Sound. + * + * @param sound given Sound + * @return the duration of the Sound + * @example get_duration(make_sound(t => Math_sin(2 * Math_PI * 440 * t), 5)); // Returns 5 + */ +export function get_duration(sound: Sound): number { + return tail(sound); } diff --git a/src/tabs/Plotly/index.tsx b/src/tabs/Plotly/index.tsx index 10adfebd2..b925b787d 100644 --- a/src/tabs/Plotly/index.tsx +++ b/src/tabs/Plotly/index.tsx @@ -55,7 +55,10 @@ class Plotly extends React.Component { drawnPlots.map((drawnPlot: any, id:number) => { const divId = `plotDiv${id}`; return ( -
+
this.handleOpen(drawnPlot)}>Click here to open Modal
, label: 'Plotly Test Tab', iconName: 'scatter-plot', -}; +}; \ No newline at end of file