aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/Network.ts7
-rw-r--r--src/client/documents/Documents.ts14
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/views/nodes/DataViz.tsx21
-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.tsx90
-rw-r--r--src/client/views/nodes/DataVizBox/DrawHelper.ts247
-rw-r--r--src/client/views/nodes/DataVizBox/HistogramBox.scss18
-rw-r--r--src/client/views/nodes/DataVizBox/HistogramBox.tsx159
-rw-r--r--src/client/views/nodes/DataVizBox/TableBox.scss22
-rw-r--r--src/client/views/nodes/DataVizBox/TableBox.tsx37
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/fields/URLField.ts1
-rw-r--r--src/server/ApiManagers/UploadManager.ts1
-rw-r--r--src/server/DashUploadUtils.ts18
-rw-r--r--src/server/DataVizUtils.ts13
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