diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/Network.ts | 7 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 14 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
-rw-r--r-- | src/client/views/nodes/DataViz.tsx | 21 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DataVizBox.scss (renamed from src/client/views/nodes/DataViz.scss) | 0 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DataVizBox.tsx | 90 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DrawHelper.ts | 247 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramBox.scss | 18 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/HistogramBox.tsx | 159 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/TableBox.scss | 22 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/TableBox.tsx | 37 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 2 | ||||
-rw-r--r-- | src/fields/URLField.ts | 1 | ||||
-rw-r--r-- | src/server/ApiManagers/UploadManager.ts | 1 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 18 | ||||
-rw-r--r-- | src/server/DataVizUtils.ts | 13 |
16 files changed, 624 insertions, 28 deletions
diff --git a/src/client/Network.ts b/src/client/Network.ts index 3597e7b2b..224420125 100644 --- a/src/client/Network.ts +++ b/src/client/Network.ts @@ -18,6 +18,13 @@ export namespace Networking { return requestPromise.post(options); } + /** + * Handles uploading basic file types to server and makes the API call to "/uploadFormData" endpoint + * with the mapping of GUID to filem as parameters. + * + * @param files the files to be uploaded to the server + * @returns the response as a json from the server + */ export async function UploadFilesToServer<T extends Upload.FileInformation = Upload.FileInformation>(files: File | File[]): Promise<Upload.FileResponse<T>[]> { const formData = new FormData(); if (Array.isArray(files)) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4690d856d..65b3db0e2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -12,7 +12,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; +import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { inheritParentAcls, SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; @@ -37,7 +37,7 @@ import { AudioBox } from "../views/nodes/AudioBox"; import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; -import { DataVizBox } from "../views/nodes/DataViz"; +import { DataVizBox } from "../views/nodes/DataVizBox/DataVizBox"; import { DocFocusOptions } from "../views/nodes/DocumentView"; import { EquationBox } from "../views/nodes/EquationBox"; import { FieldViewProps } from "../views/nodes/FieldView"; @@ -904,8 +904,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } - export function DataVizDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), undefined, { title: "Data Viz", ...options }); + export function DataVizDocument(url: string, options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: "Data Viz", ...options }); } export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) { @@ -1217,6 +1217,11 @@ export namespace DocUtils { if (!options._width) options._width = 400; if (!options._height) options._height = (options._width as number) * 1200 / 927; } + if (type.indexOf("csv") !== -1) { + ctor = Docs.Create.DataVizDocument; + if (!options._width) options._width = 400; + if (!options._height) options._height = (options._width as number) * 1200 / 927; + } //TODO:al+glr // if (type.indexOf("map") !== -1) { // ctor = Docs.Create.MapDocument; @@ -1242,6 +1247,7 @@ export namespace DocUtils { ctor = Docs.Create.WebDocument; options = { ...options, _width: 400, _height: 512, title: path, }; } + return ctor ? ctor(path, options) : undefined; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a39081d10..dca77250c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -286,7 +286,7 @@ export class CurrentUserUtils { {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List<string>(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, - {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, + // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx deleted file mode 100644 index d9541dba0..000000000 --- a/src/client/views/nodes/DataViz.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { ViewBoxBaseComponent } from '../DocComponent'; -import "./DataViz.scss"; -import { FieldView, FieldViewProps } from "./FieldView"; - -@observer -export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() { - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } - - render() { - return ( - <div > - <div> - Hi - </div> - </div> - ); - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/DataViz.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss index e69de29bb..e69de29bb 100644 --- a/src/client/views/nodes/DataViz.scss +++ b/src/client/views/nodes/DataVizBox/DataVizBox.scss diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx new file mode 100644 index 000000000..592723ee9 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -0,0 +1,90 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { StrCast } from "../../../../fields/Types"; +import { ViewBoxBaseComponent } from "../../DocComponent"; +import { FieldViewProps, FieldView } from "../FieldView"; +import "./DataVizBox.scss"; +import { HistogramBox } from "./HistogramBox"; +import { TableBox } from "./TableBox"; + +enum DataVizView { + TABLE = "table", + HISTOGRAM= "histogram" +} + + +@observer +export class DataVizBox extends ViewBoxBaseComponent<FieldViewProps>() { + @observable private pairs: {x: number, y:number}[] = [{x: 1, y:2}]; + + // TODO: nda - make this use enum values instead + // @observable private currView: DataVizView = DataVizView.TABLE; + @computed get currView() { + if (this.rootDoc._dataVizView) { + return StrCast(this.rootDoc._dataVizView); + } else { + return "table"; + } + } + + constructor(props: any) { + super(props); + if (!this.rootDoc._dataVizView) { + // TODO: nda - this might not always want to default to "table" + this.rootDoc._dataVizView = "table"; + } + } + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } + + @action + private createPairs() { + const xVals: number[] = [0, 1, 2, 3, 4, 5]; + // const yVals: number[] = [10, 20, 30, 40, 50, 60]; + const yVals: number[] = [1, 2, 3, 4, 5, 6]; + let pairs: { + x: number, + y:number + }[] = []; + if (xVals.length != yVals.length) return pairs; + for (let i = 0; i < xVals.length; i++) { + pairs.push({x: xVals[i], y: yVals[i]}); + } + this.pairs = pairs; + return pairs; + } + + @computed get selectView() { + switch(this.currView) { + case "table": + return (<TableBox pairs={this.pairs} />) + case "histogram": + return (<HistogramBox rootDoc={this.rootDoc} pairs={this.pairs}/>) + } + } + + @computed get pairVals() { + return this.createPairs(); + } + + componentDidMount() { + this.createPairs(); + } + + // handle changing the view using a button + @action changeViewHandler(e: React.MouseEvent<HTMLButtonElement>) { + e.preventDefault(); + e.stopPropagation(); + this.rootDoc._dataVizView = this.currView == "table" ? "histogram" : "table"; + } + + render() { + return ( + <div className="dataViz"> + <button onClick={(e) => this.changeViewHandler(e)}>Change View</button> + {this.selectView} + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DrawHelper.ts b/src/client/views/nodes/DataVizBox/DrawHelper.ts new file mode 100644 index 000000000..595cecebf --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DrawHelper.ts @@ -0,0 +1,247 @@ +export class PIXIPoint { + public get x() { return this.coords[0]; } + public get y() { return this.coords[1]; } + public set x(value: number) { this.coords[0] = value; } + public set y(value: number) { this.coords[1] = value; } + public coords: number[] = [0, 0]; + constructor(x: number, y: number) { + this.coords[0] = x; + this.coords[1] = y; + } +} + +export class PIXIRectangle { + public x: number; + public y: number; + public width: number; + public height: number; + public get left() { return this.x; } + public get right() { return this.x + this.width; } + public get top() { return this.y; } + public get bottom() { return this.top + this.height; } + public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); } + constructor(x: number, y: number, width: number, height: number) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +export class MathUtil { + + public static EPSILON: number = 0.001; + + public static Sign(value: number): number { + return value >= 0 ? 1 : -1; + } + + public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x += p2.x; + p1.y += p2.y; + return p1; + } + else { + return new PIXIPoint(p1.x + p2.x, p1.y + p2.y); + } + } + + public static Perp(p1: PIXIPoint): PIXIPoint { + return new PIXIPoint(-p1.y, p1.x); + } + + public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x /= by; + p1.y /= by; + return p1; + } + else { + return new PIXIPoint(p1.x / by, p1.y / by); + } + } + + public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) { + if (inline) { + p1.x *= by; + p1.y *= by; + return p1; + } + else { + return new PIXIPoint(p1.x * by, p1.y * by); + } + } + + public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x -= p2.x; + p1.y -= p2.y; + return p1; + } + else { + return new PIXIPoint(p1.x - p2.x, p1.y - p2.y); + } + } + + public static Area(rect: PIXIRectangle): number { + return rect.width * rect.height; + } + + public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) { + // Return minimum distance between line segment vw and point p + var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt + if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case + // Consider the line extending the segment, parameterized as v + t (w - v). + // We find projection of point p onto the line. + // It falls where t = [(p-v) . (w-v)] / |w-v|^2 + // We clamp t from [0,1] to handle points outside the segment vw. + var dot = MathUtil.Dot( + MathUtil.SubtractPoint(p, v), + MathUtil.SubtractPoint(w, v)) / l2; + var t = Math.max(0, Math.min(1, dot)); + // Projection falls on the segment + var projection = MathUtil.AddPoint(v, + MathUtil.MultiplyConstant( + MathUtil.SubtractPoint(w, v), t)); + return MathUtil.Dist(p, projection); + } + + public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined { + var a1 = pe1.y - ps1.y; + var b1 = ps1.x - pe1.x; + + var a2 = pe2.y - ps2.y; + var b2 = ps2.x - pe2.x; + + var delta = a1 * b2 - a2 * b1; + if (delta === 0) { + return undefined; + } + var c2 = a2 * ps2.x + b2 * ps2.y; + var c1 = a1 * ps1.x + b1 * ps1.y; + var invdelta = 1 / delta; + return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta); + } + + public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean { + if (p.x < rect.left - this.EPSILON) { + return false; + } + if (p.x > rect.right + this.EPSILON) { + return false; + } + if (p.y < rect.top - this.EPSILON) { + return false; + } + if (p.y > rect.bottom + this.EPSILON) { + return false; + } + + return true; + } + + public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> { + var r1 = new PIXIPoint(rect.left, rect.top); + var r2 = new PIXIPoint(rect.right, rect.top); + var r3 = new PIXIPoint(rect.right, rect.bottom); + var r4 = new PIXIPoint(rect.left, rect.bottom); + var ret = new Array<PIXIPoint>(); + var dist = this.Dist(lineFrom, lineTo); + var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + return ret; + } + + public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle { + const left = Math.max(rect1.x, rect2.x); + const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width); + const top = Math.max(rect1.y, rect2.y); + const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); + return new PIXIRectangle(left, top, right - left, bottom - top); + } + + public static Dist(p1: PIXIPoint, p2: PIXIPoint): number { + return Math.sqrt(MathUtil.DistSquared(p1, p2)); + } + + public static Dot(p1: PIXIPoint, p2: PIXIPoint): number { + return p1.x * p2.x + p1.y * p2.y; + } + + public static Normalize(p1: PIXIPoint) { + var d = this.Length(p1); + return new PIXIPoint(p1.x / d, p1.y / d); + } + + public static Length(p1: PIXIPoint): number { + return Math.sqrt(p1.x * p1.x + p1.y * p1.y); + } + + public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number { + const a = p1.x - p2.x; + const b = p1.y - p2.y; + return (a * a + b * b); + } + + public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean { + return !(r2.x > r1.x + r1.width || + r2.x + r2.width < r1.x || + r2.y > r1.y + r1.height || + r2.y + r2.height < r1.y); + } + + public static ArgMin(temp: number[]): number { + let index = 0; + let value = temp[0]; + for (let i = 1; i < temp.length; i++) { + if (temp[i] < value) { + value = temp[i]; + index = i; + } + } + return index; + } + + public static ArgMax(temp: number[]): number { + let index = 0; + let value = temp[0]; + for (let i = 1; i < temp.length; i++) { + if (temp[i] > value) { + value = temp[i]; + index = i; + } + } + return index; + } + + public static Combinations<T>(chars: T[]) { + let result = new Array<T>(); + let f = (prefix: any, chars: any) => { + for (let i = 0; i < chars.length; i++) { + result.push(prefix.concat(chars[i])); + f(prefix.concat(chars[i]), chars.slice(i + 1)); + } + }; + f([], chars); + return result; + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.scss b/src/client/views/nodes/DataVizBox/HistogramBox.scss new file mode 100644 index 000000000..5aac9dc77 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBox.scss @@ -0,0 +1,18 @@ +// change the stroke color of line-svg class +.svgLine { + position: absolute; + background: darkGray; + stroke: #000; + stroke-width: 1px; + width:100%; + height:100%; + opacity: 0.4; +} + +.svgContainer { + position: absolute; + top:0; + left:0; + width:100%; + height: 100%; +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx new file mode 100644 index 000000000..00dc2ef46 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBox.tsx @@ -0,0 +1,159 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { Doc } from "../../../../fields/Doc"; +import { NumCast } from "../../../../fields/Types"; +import "./HistogramBox.scss"; + +interface HistogramBoxProps { + rootDoc: Doc; + pairs: { + x: number, + y: number + }[] +} + + +export class HistogramBox extends React.Component<HistogramBoxProps> { + + private origin = {x: 0.1 * this.width, y: 0.9 * this.height}; + + @computed get width() { + return NumCast(this.props.rootDoc.width); + } + + @computed get height() { + return NumCast(this.props.rootDoc.height); + } + + @computed get x() { + return NumCast(this.props.rootDoc.x); + } + + @computed get y() { + return NumCast(this.props.rootDoc.y); + } + + @computed get generatePoints() { + // evenly distribute points along the x axis + const xVals: number[] = this.props.pairs.map(p => p.x); + const yVals: number[] = this.props.pairs.map(p => p.y); + + const xMin = Math.min(...xVals); + const xMax = Math.max(...xVals); + const yMin = Math.min(...yVals); + const yMax = Math.max(...yVals); + + const xRange = xMax - xMin; + const yRange = yMax - yMin; + + const xScale = this.width / xRange; + const yScale = this.height / yRange; + + const xOffset = (this.x + (0.1 * this.width)) - xMin * xScale; + const yOffset = (this.y + (0.25 * this.height)) - yMin * yScale; + + const points: { + x: number, + y: number + }[] = this.props.pairs.map(p => { + return { + x: (p.x * xScale + xOffset) + this.origin.x, + y: (p.y * yScale + yOffset) + } + }); + + return points; + } + + @computed get generateGraphLine() { + const points = this.generatePoints; + // loop through points and create a line from each point to the next + let lines: { + x1: number, + y1: number, + x2: number, + y2: number + }[] = []; + for (let i = 0; i < points.length - 1; i++) { + lines.push({ + x1: points[i].x, + y1: points[i].y, + x2: points[i + 1].x, + y2: points[i + 1].y + }); + } + // generate array of svg with lines + let svgLines: JSX.Element[] = []; + for (let i = 0; i < lines.length; i++) { + svgLines.push( + <line + className="svgLine" + key={i} + x1={lines[i].x1} + y1={lines[i].y1} + x2={lines[i].x2} + y2={lines[i].y2} + stroke="black" + strokeWidth={2} + /> + ); + } + + let res = []; + for (let i = 0; i < svgLines.length; i++) { + res.push(<svg className="svgContainer">{svgLines[i]}</svg>) + } + return res; + } + + @computed get generateAxes() { + + const xAxis = { + x1: 0.1 * this.width, + x2: 0.9 * this.width, + y1: 0.9 * this.height, + y2: 0.9 * this.height, + }; + + const yAxis = { + x1: 0.1 * this.width, + x2: 0.1 * this.width, + y1: 0.25 * this.height, + y2: 0.9 * this.height, + }; + + + return ( + [ + (<svg className="svgContainer"> + {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={this.width - (0.1 * this.width)} y2={xAxis} /> */} + <line className="svgLine" x1={xAxis.x1} y1={xAxis.y1} x2={xAxis.x2} y2={xAxis.y2}/> + + {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */} + </svg>), + ( + <svg className="svgContainer"> + <line className="svgLine" x1={yAxis.x1} y1={yAxis.y1} x2={yAxis.x2} y2={yAxis.y2} /> + {/* <line className="svgLine" x1={yAxis} y1={xAxis} x2={yAxis} y2={this.y + 50} /> */} + </svg>) + ] + ) + } + + + render() { + return ( + <div>histogram box + {/* <svg className="svgContainer"> + {this.generateSVGLine} + </svg> */} + {this.generateAxes[0]} + {this.generateAxes[1]} + {this.generateGraphLine.map(line => line)} + </div> + ) + + } + +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/TableBox.scss b/src/client/views/nodes/DataVizBox/TableBox.scss new file mode 100644 index 000000000..1264d6a46 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/TableBox.scss @@ -0,0 +1,22 @@ +.table { + margin-top: 10px; + margin-bottom: 10px; + margin-left: 10px; + margin-right: 10px; +} + +.table-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 5px; + border-bottom: 1px solid #ccc; +} + +.table-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +}
\ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/TableBox.tsx b/src/client/views/nodes/DataVizBox/TableBox.tsx new file mode 100644 index 000000000..dfa8262d8 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/TableBox.tsx @@ -0,0 +1,37 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; + +interface TableBoxProps { + pairs: {x: number, y:number}[] +} + + +export class TableBox extends React.Component<TableBoxProps> { + + + + render() { + return ( + <div className="table-container"> + <table className="table"> + <thead> + <tr className="table-row"> + <th>x</th> + <th>y</th> + </tr> + </thead> + <tbody> + {this.props.pairs.map(p => { + return (<tr className="table-row"> + <td>{p.x}</td> + <td>{p.y}</td> + </tr>) + })} + </tbody> + </table> + </div> + ) + } + +}
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 371d85a32..96ac3e332 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -19,7 +19,7 @@ import { AudioBox } from "./AudioBox"; import { FontIconBox } from "./button/FontIconBox"; import { ColorBox } from "./ColorBox"; import { ComparisonBox } from "./ComparisonBox"; -import { DataVizBox } from "./DataViz"; +import { DataVizBox } from "./DataVizBox/DataVizBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { EquationBox } from "./EquationBox"; diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 3e7542e46..36dd56a1a 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -59,6 +59,7 @@ export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short @scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } @scriptingGlobal @Deserializable("web") export class WebField extends URLField { } @scriptingGlobal @Deserializable("map") export class MapField extends URLField { } +@scriptingGlobal @Deserializable("csv") export class CsvField extends URLField { } @scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } @scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index e7b7056a1..04a11f410 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -24,6 +24,7 @@ export enum Directory { text = "text", pdf_thumbnails = "pdf_thumbnails", audio = "audio", + csv = "csv", } export function serverPathToFile(directory: Directory, filename: string) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 552ab57a5..5f46bcc88 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -17,6 +17,7 @@ import { resolvedServerUrl } from "./server_Initialization"; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); +import { csvParser } from './DataVizUtils'; const { exec } = require("child_process"); const parse = require('pdf-parse'); const ffmpeg = require("fluent-ffmpeg"); @@ -85,7 +86,7 @@ export namespace DashUploadUtils { const category = types[0]; let format = `.${types[1]}`; console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`)); - + switch (category) { case "image": if (imageFormats.includes(format)) { @@ -116,6 +117,11 @@ export namespace DashUploadUtils { if (audioFormats.includes(format)) { return UploadAudio(file, format); } + case "text": + if (types[1] == "csv") { + return UploadCsv(file); + } + } console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`)); @@ -135,6 +141,16 @@ export namespace DashUploadUtils { return MoveParsedFile(file, Directory.pdfs, undefined, result.text); } + async function UploadCsv(file: File) { + const { path: sourcePath } = file; + // read the file as a string + const data = readFileSync(sourcePath, 'utf8'); + // split the string into an array of lines + return MoveParsedFile(file, Directory.csv, undefined, data); + // console.log(csvParser(data)); + + } + const manualSuffixes = [".webm"]; async function UploadAudio(file: File, format: string) { diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts new file mode 100644 index 000000000..4fd0ca6ff --- /dev/null +++ b/src/server/DataVizUtils.ts @@ -0,0 +1,13 @@ +export function csvParser(csv: string) { + const lines = csv.split("\n"); + const headers = lines[0].split(","); + const data = lines.slice(1).map(line => { + const values = line.split(","); + const obj: any = {}; + for (let i = 0; i < headers.length; i++) { + obj[headers[i]] = values[i]; + } + return obj; + }); + return data; +}
\ No newline at end of file |