diff --git a/viz-lib/package-lock.json b/viz-lib/package-lock.json index bfdf453a93..8df839bf47 100644 --- a/viz-lib/package-lock.json +++ b/viz-lib/package-lock.json @@ -1,6 +1,6 @@ { "name": "@redash/viz", - "version": "0.1.1", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -8037,6 +8037,16 @@ "minimalistic-assert": "^1.0.1" } }, + "highcharts": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.0.0.tgz", + "integrity": "sha512-a14PrTraVaoSNyaRPhLF/jJ3/09rJwfUZedLgZOkzozGz4K/H8WtWNJtUZt/tc5QiJkaZHw4YX7vvuO98s+EoA==" + }, + "highcharts-react-official": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.1.0.tgz", + "integrity": "sha512-CkWJHrVMOc6CT8KFu1dR+a0w5OxCVKKgZUNWtEi5TmR0xqBDIDe+RyM652MAN/jBYppxMo6TCUVlRObCyWAn0Q==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", diff --git a/viz-lib/package.json b/viz-lib/package.json index 4031dd455b..0416a22d86 100644 --- a/viz-lib/package.json +++ b/viz-lib/package.json @@ -86,6 +86,8 @@ "debug": "^3.1.0", "dompurify": "^2.0.7", "font-awesome": "^4.7.0", + "highcharts": "^10.0.0", + "highcharts-react-official": "^3.1.0", "hoist-non-react-statics": "^3.3.0", "leaflet": "^1.2.0", "leaflet-fullscreen": "^1.0.2", diff --git a/viz-lib/src/visualizations/gantt-chart/Editor.tsx b/viz-lib/src/visualizations/gantt-chart/Editor.tsx new file mode 100644 index 0000000000..ecc596ca31 --- /dev/null +++ b/viz-lib/src/visualizations/gantt-chart/Editor.tsx @@ -0,0 +1,33 @@ +import { map, merge } from "lodash"; +import React from "react"; +import * as Grid from "antd/lib/grid"; +import { Section, Select, InputNumber, ControlLabel } from "@/components/visualizations/editor"; +import { EditorPropTypes } from "@/visualizations/prop-types"; + +export default function Editor({ options, data, onOptionsChange }: any) { + const optionsChanged = (newOptions: any) => { + onOptionsChange(merge({}, options, newOptions)); + }; + + return ( + + {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} +
+ +
+
+ ); +} + +Editor.propTypes = EditorPropTypes; diff --git a/viz-lib/src/visualizations/gantt-chart/Renderer.tsx b/viz-lib/src/visualizations/gantt-chart/Renderer.tsx new file mode 100644 index 0000000000..05132dc3cd --- /dev/null +++ b/viz-lib/src/visualizations/gantt-chart/Renderer.tsx @@ -0,0 +1,103 @@ +import React, { useMemo, useState, useEffect } from "react"; +import { RendererPropTypes } from "@/visualizations/prop-types"; + +import { groupBy } from "lodash"; + +import HighchartsReact from "highcharts-react-official"; +// Import Highcharts +import Highcharts from "highcharts"; +import highchartsGantt from "highcharts/modules/gantt"; + +highchartsGantt(Highcharts); + +import "./renderer.less"; + +function prepareData(data: any) { + const colors: any = { + "Early Engagement": "#e1ebf3", + "Proponent Time: Project Description": "#ccffff", + "Readiness Decision": "#c3d7e8", + "Process Planning": "#a6c3dd", + "Proponent Time: Application Development": "#ccffff", + "Application Development & Review": "#faeadc", + "Proponent Time: Revised Application": "#ccffff", + "Effects Assessment": "#f6d5b9", + Recommendation: "#f2c096", + "Referral/Decision": "#f2c096", + }; + const projects = groupBy(data, (item: any) => item.project); + const series: Array = []; + let projectIndex = 0; + + for (const project in projects) { + const projectData: any = projects[project]; + const projectId: string = `${projectData[0].project_id}`; + const projectSeries: any = { + name: projectData[0].project, + data: [], + }; + + projectData.forEach((phase: any) => { + projectSeries.data.push({ + id: phase.phase_id, + name: phase.phase, + start: new Date(phase.phase_start).getTime(), + end: new Date(phase.phase_end).getTime(), + y: projectIndex, + color: colors[phase.phase], + }); + }); + series.push(projectSeries); + projectIndex += 1; + } + + return [series, Object.keys(projects)]; +} + +export default function Renderer({ data, options }: any) { + const [tasks, projects] = prepareData(data.rows); + const first = tasks[0]; + const last = tasks[tasks.length - 1]; + const day = 1000 * 60 * 60 * 24; + const chartData: Highcharts.Options = { + title: { + text: "Highcharts Gantt With Subtasks", + }, + xAxis: [ + { + tickInterval: 1000 * 60 * 60 * 24 * 30, // Month + labels: { + format: "{value:%b}", + style: { + fontSize: "8px", + }, + autoRotation: [-90], + }, + min: first.data[0].start - day * 15, + max: last.data[last.data.length - 1].end + day * 15, + currentDateIndicator: false, + }, + { + tickInterval: 1000 * 60 * 60 * 24 * 365, // Year + labels: { + format: "{value:%Y}", + }, + linkedTo: 0, + }, + ], + yAxis: { + categories: projects, + }, + series: tasks, + }; + + console.log(chartData); + + return ( +
+ +
+ ); +} + +Renderer.propTypes = RendererPropTypes; diff --git a/viz-lib/src/visualizations/gantt-chart/index.ts b/viz-lib/src/visualizations/gantt-chart/index.ts new file mode 100644 index 0000000000..d2ed788294 --- /dev/null +++ b/viz-lib/src/visualizations/gantt-chart/index.ts @@ -0,0 +1,18 @@ +import { merge } from "lodash"; + +import Renderer from "./Renderer"; +import Editor from "./Editor"; + +const DEFAULT_OPTIONS = { + projectName: "", +}; + +export default { + type: "GANTT_CHART", + name: "Gantt Chart", + getOptions: (options: any) => merge({}, DEFAULT_OPTIONS, options), + Renderer, + Editor, + + defaultRows: 8, +}; diff --git a/viz-lib/src/visualizations/gantt-chart/renderer.less b/viz-lib/src/visualizations/gantt-chart/renderer.less new file mode 100644 index 0000000000..aee6afcced --- /dev/null +++ b/viz-lib/src/visualizations/gantt-chart/renderer.less @@ -0,0 +1,12 @@ +.word-cloud-visualization-container { + overflow: hidden; + height: 400px; + display: flex; + align-items: center; + justify-content: center; + + svg { + transform-origin: center center; + flex: 0 0 auto; + } +} diff --git a/viz-lib/src/visualizations/registeredVisualizations.ts b/viz-lib/src/visualizations/registeredVisualizations.ts index 2e6fb7df7d..7e9e4f258d 100644 --- a/viz-lib/src/visualizations/registeredVisualizations.ts +++ b/viz-lib/src/visualizations/registeredVisualizations.ts @@ -14,6 +14,7 @@ import sankeyVisualization from "./sankey"; import sunburstVisualization from "./sunburst"; import tableVisualization from "./table"; import wordCloudVisualization from "./word-cloud"; +import ganttChartVisualization from "./gantt-chart"; type VisualizationConfig = { type: string; @@ -91,6 +92,7 @@ each( sunburstVisualization, tableVisualization, wordCloudVisualization, + ganttChartVisualization, ]), registerVisualization );