From c9cdf44b34a2a38da2ee35e24019b9f91f70a8e7 Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Wed, 22 Sep 2021 16:14:59 +0800 Subject: [PATCH 01/10] fix: graphic walker use visual-insights@0.6.2 --- packages/frontend/package.json | 2 +- packages/graphic-walker/package.json | 3 +- packages/graphic-walker/src/App.tsx | 78 ++++--------------- .../src/Fields/useFieldsState.tsx | 40 ++++++++++ .../src/InsightBoard/selectionSpec.ts | 2 +- .../src/InsightBoard/std2vegaSpec.ts | 6 +- .../graphic-walker/src/dataSource/index.tsx | 42 ++++++---- .../graphic-walker/src/dataSource/table.tsx | 14 ++-- packages/graphic-walker/src/insights.ts | 34 +++----- packages/graphic-walker/src/interfaces.ts | 6 +- packages/graphic-walker/src/services.ts | 7 +- .../src/workers/explainer.worker.js | 5 +- yarn.lock | 8 +- 13 files changed, 121 insertions(+), 126 deletions(-) create mode 100644 packages/graphic-walker/src/Fields/useFieldsState.tsx diff --git a/packages/frontend/package.json b/packages/frontend/package.json index d9dd6d36..8a589655 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -47,7 +47,7 @@ "vega": "^5.19.1", "vega-embed": "^6.15.1", "vega-lite": "^4.17.0", - "visual-insights": "0.6.2", + "visual-insights": "0.6.3", "web-vitals": "^0.2.4", "worker-loader": "^3.0.7", "yarn": "^1.19.0" diff --git a/packages/graphic-walker/package.json b/packages/graphic-walker/package.json index 213d210c..a1e5bd7f 100644 --- a/packages/graphic-walker/package.json +++ b/packages/graphic-walker/package.json @@ -29,7 +29,8 @@ "styled-components": "^5.3.0", "vega": "^5.20.2", "vega-embed": "^6.18.2", - "vega-lite": "^5.1.0" + "vega-lite": "^5.1.0", + "visual-insights": "0.6.3" }, "devDependencies": { "@rollup/plugin-typescript": "^8.2.5", diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index 6e0a4d27..35d6dc9a 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -1,12 +1,13 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import DraggableFields, { DraggableFieldState } from './Fields'; -import { Record, Filters, Field, IField } from './interfaces'; +import React, { useState, useEffect, useCallback } from 'react'; +import DraggableFields from './Fields'; +import { Record, Filters, Field, IMutField } from './interfaces'; import ReactVega from './vis/react-vega'; import { GEMO_TYPES } from './config'; import { LiteForm } from './components/liteForm'; import { Container } from './components/container'; import ClickMenu from './components/clickMenu'; import InsightBoard from './InsightBoard'; +import { useFieldsState } from './Fields/useFieldsState' import { Button, DropdownSelect, Checkbox } from '@tableau/tableau-ui'; import { ProgressIndicator } from '@fluentui/react' import Modal from './components/modal'; @@ -14,27 +15,15 @@ import DataSourcePanel from './dataSource/index'; import { useLocalState } from './store'; import { preAnalysis, destroyWorker } from './services' - -const INIT_DF_STATE: DraggableFieldState = { - fields: [], - rows: [], - columns: [], - color: [], - opacity: [], - size: [], -}; - export interface EditorProps { dataSource?: Record[]; - dimensions?: string[]; - measures?: string[]; + rawFields?: IMutField[]; } const App: React.FC = props => { - const { dataSource = [], dimensions = [], measures = [] } = props; + const { dataSource = [], rawFields = [] } = props; const [GS, updateGS] = useLocalState(); const [fields, setFields] = useState([]); - const [fstate, setFstate] = useState(INIT_DF_STATE); const [geomType, setGeomType] = useState(GEMO_TYPES[0].value); const [aggregated, setAggregated] = useState(true); const [position, setPosition] = useState<[number, number]>([0, 0]); @@ -43,48 +32,29 @@ const App: React.FC = props => { const [filters, setFilters] = useState({}); const [showDSPanel, setShowDSPanel] = useState(false); const [insightReady, setInsightReady] = useState(true); - // const [ds, setDS] = useState({ dimensions: [], measures: [], dataSource: []}); const [newDBIndex, setNewDBIndex] = useState(0); - // useEffect(() => { - // const target = DS_LIST.find(d => d.value === dsKey); - // if (target) { - // setDS(target.service()) - // } - // }, [dsKey]) + + const { fstate, setFstate, viewDimensions, viewMeasures } = useFieldsState(); + useEffect(() => { if (dataSource.length > 0) { - const fields: IField[] = []; - dimensions.forEach(f => { - fields.push({ - key: f, - type: 'D', - analyticType: 'dimension' - }) - }); - measures.forEach(f => { - fields.push({ - key: f, - type: 'D', - analyticType: 'measure' - }) - }); updateGS(state => { state.dataBase = [ { id: 'default', name: 'context dataset', dataSource: dataSource, - fields + rawFields } ] }) } - }, [dataSource, dimensions, measures]) + }, [dataSource, rawFields]) useEffect(() => { const fs: Field[] = []; const ds = GS.dataBase[GS.currentDBIndex]; if (ds) { - ds.fields.forEach((f) => { + ds.rawFields.forEach((f) => { fs.push({ id: f.key, name: f.key, @@ -96,33 +66,13 @@ const App: React.FC = props => { } }, [GS.currentDBIndex, GS.dataBase]); - const viewDimensions = useMemo(() => { - return [ - ...fstate.rows, - ...fstate.columns, - ...fstate.color, - ...fstate.opacity, - ...fstate.size - ].filter(f => f.type === 'D'); - }, [fstate]) - const viewMeasures = useMemo(() => { - return [ - ...fstate.rows, - ...fstate.columns, - ...fstate.color, - ...fstate.opacity, - ...fstate.size, - ].filter((f) => f.type === 'M'); - }, [fstate]); - useEffect(() => { const ds = GS.dataBase[GS.currentDBIndex]; if (ds) { setInsightReady(false) preAnalysis({ dataSource: ds.dataSource, - dimensions: ds.fields.filter(f => f.analyticType === 'dimension').map(f => f.key), - measures: ds.fields.filter(f => f.analyticType === 'measure').map(f => f.key) + fields: ds.rawFields }).then(() => { setInsightReady(true); }) @@ -139,7 +89,7 @@ const App: React.FC = props => { id: 'ds_' + newLastIndex, name: '新数据源' + newLastIndex, dataSource: [], - fields: [] + rawFields: [] }) setNewDBIndex(newLastIndex); }) diff --git a/packages/graphic-walker/src/Fields/useFieldsState.tsx b/packages/graphic-walker/src/Fields/useFieldsState.tsx new file mode 100644 index 00000000..e77db6b1 --- /dev/null +++ b/packages/graphic-walker/src/Fields/useFieldsState.tsx @@ -0,0 +1,40 @@ +import React, { useMemo, useState } from 'react'; +import { Field } from '../interfaces'; +import { DraggableFieldState } from './index'; + +const INIT_DF_STATE: DraggableFieldState = { + fields: [], + rows: [], + columns: [], + color: [], + opacity: [], + size: [], + }; + +export function useFieldsState() { + const [fstate, setFstate] = useState(INIT_DF_STATE); + const viewDimensions = useMemo(() => { + return [ + ...fstate.rows, + ...fstate.columns, + ...fstate.color, + ...fstate.opacity, + ...fstate.size + ].filter(f => f.type === 'D'); + }, [fstate]) + const viewMeasures = useMemo(() => { + return [ + ...fstate.rows, + ...fstate.columns, + ...fstate.color, + ...fstate.opacity, + ...fstate.size, + ].filter((f) => f.type === 'M'); + }, [fstate]); + return { + fstate, + setFstate, + viewDimensions, + viewMeasures + } +} \ No newline at end of file diff --git a/packages/graphic-walker/src/InsightBoard/selectionSpec.ts b/packages/graphic-walker/src/InsightBoard/selectionSpec.ts index 2693ecd6..14cf6142 100644 --- a/packages/graphic-walker/src/InsightBoard/selectionSpec.ts +++ b/packages/graphic-walker/src/InsightBoard/selectionSpec.ts @@ -1,4 +1,4 @@ -import { Specification } from 'visual-insights/build/esm/commonTypes'; +import { Specification } from 'visual-insights'; import { Record, SemanticType } from '../interfaces'; export const geomTypeMap: { [key: string]: any } = { interval: 'bar', diff --git a/packages/graphic-walker/src/InsightBoard/std2vegaSpec.ts b/packages/graphic-walker/src/InsightBoard/std2vegaSpec.ts index 93dbed79..9ae1cf41 100644 --- a/packages/graphic-walker/src/InsightBoard/std2vegaSpec.ts +++ b/packages/graphic-walker/src/InsightBoard/std2vegaSpec.ts @@ -1,6 +1,6 @@ -import { Specification } from 'visual-insights/build/esm/commonTypes'; +import { Specification } from 'visual-insights'; import { Record, SemanticType } from '../interfaces'; -import { deepcopy } from 'visual-insights/build/esm/utils'; +import { Utils } from 'visual-insights'; import { IPredicate } from '../utils'; export type IReasonType = 'selection_dim_distribution' | 'selection_mea_distribution' | 'children_major_factor' | 'children_outlier'; export const geomTypeMap: { [key: string]: any } = { @@ -129,7 +129,7 @@ export function baseVis( ...basicSpec, }; } - const basicSpecFilter = deepcopy(basicSpec); + const basicSpecFilter = Utils.deepcopy(basicSpec); basicSpec.mark.opacity = 0.9; basicSpec.mark.color = '#8c8c8c'; // basicSpecFilter.mark.color = '#f5222d'; diff --git a/packages/graphic-walker/src/dataSource/index.tsx b/packages/graphic-walker/src/dataSource/index.tsx index 25815350..57f98570 100644 --- a/packages/graphic-walker/src/dataSource/index.tsx +++ b/packages/graphic-walker/src/dataSource/index.tsx @@ -1,7 +1,7 @@ import React, { useRef, useState, useCallback } from 'react'; import { Button, TextField } from '@tableau/tableau-ui'; import { FileReader } from '@kanaries/web-data-loader'; -import { Record, IField } from '../interfaces'; +import { Record, IField, IMutField } from '../interfaces'; import { Insight } from 'visual-insights'; import Table from './table'; import styled from 'styled-components'; @@ -30,7 +30,7 @@ const Container = styled.div` `; function transData(dataSource: Record[]): { dataSource: Record[]; - fields: IField[] + fields: IMutField[] } { if (dataSource.length === 0) return { dataSource: [], @@ -38,10 +38,18 @@ function transData(dataSource: Record[]): { }; let ans: Record[] = []; const keys = Object.keys(dataSource[0]); + // TODO: 冗余设计,单变量统计被进行了多次重复计算。另外对于这种不完整的分析任务,不建议使用VIEngine。 const vie = new Insight.VIEngine(); - vie.setDataSource(dataSource) - .setFieldKeys(keys) - .buildfieldsSummary(); + vie.setData(dataSource) + .setFields(keys.map(k => ({ + key: k, + analyticType: '?', + dataType: '?', + semanticType: '?' + }))) + // TODO: 结合上面的TODO,讨论,VIEngine是否要提供不需要进行univarSelection就提供summary的接口。 + // 这里我们使用了一种非原API设计时期待的用法,即强制指定单变量选择时要全选字段。但我们无法阻止对变量的转换。 + vie.univarSelection('percent', 1); const fields = vie.fields; // console.log(fields) for (let record of dataSource) { @@ -59,8 +67,9 @@ function transData(dataSource: Record[]): { dataSource: ans, fields: fields.map(f => ({ key: f.key, - type: f.dataType, - analyticType: f.analyticType + analyticType: f.analyticType, + dataType: f.dataType, + semanticType: f.semanticType })) } } @@ -72,26 +81,29 @@ const DataSourcePanel: React.FC = props => { const { dbIndex, onSubmit } = props; const fileRef = useRef(null); const [dataSource, setDataSource] = useState([]); - const [fields, setFields] = useState([]); - const [GS, updateGS] = useLocalState(); + const [rawFields, setRawFields] = useState([]); + const [, updateGS] = useLocalState(); const [dsName, setDSName] = useState('新数据集') - const onFieldsChange = useCallback((fields: IField[]) => { - setFields(fields); + const onFieldsChange = useCallback((fields: IMutField[]) => { + setRawFields(fields); }, []) + + + const onSubmitData = useCallback(() => { updateGS(draft => { draft.dataBase[dbIndex] = { id: 'test' + dbIndex, name: dsName, - fields, + rawFields, dataSource } }) if (onSubmit) { onSubmit(); } - }, [dbIndex, fields, dataSource, dsName]) + }, [dbIndex, rawFields, dataSource, dsName]) return ( = props => { console.log(result); // TODO: need fix web-data-loader issue #2 setDataSource(result.dataSource.slice(0, -1)); - setFields(result.fields); + setRawFields(result.fields); }); } }} @@ -137,7 +149,7 @@ const DataSourcePanel: React.FC = props => { setDSName(e.target.value) }} /> - +
); } diff --git a/packages/graphic-walker/src/dataSource/table.tsx b/packages/graphic-walker/src/dataSource/table.tsx index 572ce5a4..3a874d5a 100644 --- a/packages/graphic-walker/src/dataSource/table.tsx +++ b/packages/graphic-walker/src/dataSource/table.tsx @@ -1,14 +1,14 @@ import React from 'react'; -import { Record, IField } from '../interfaces'; +import { Record, IField, IMutField } from '../interfaces'; import styled from 'styled-components'; import { DropdownSelect } from '@tableau/tableau-ui'; import produce from 'immer'; interface TableProps { - fields: IField[]; + fields: IMutField[]; dataSource: Record[]; size?: number; - onFieldsUpdate: (fields: IField[]) => void + onFieldsUpdate: (fields: IMutField[]) => void } const Container = styled.div` overflow-x: auto; @@ -55,10 +55,10 @@ const TYPE_LIST = [ label: '度量' } ]; -function getCellType(field: IField): 'number' | 'text' { - return field.type === 'number' || field.type === 'integer' ? 'number' : 'text'; +function getCellType(field: IMutField): 'number' | 'text' { + return field.dataType === 'number' || field.dataType === 'integer' ? 'number' : 'text'; } -function getHeaderType(field: IField): 'number' | 'text' { +function getHeaderType(field: IMutField): 'number' | 'text' { return field.analyticType === 'dimension'? 'text' : 'number'; } const Table: React.FC = props => { @@ -70,7 +70,7 @@ const Table: React.FC = props => { {fields.map((field, fIndex) => (
- {field.key}({field.type}) + {field.key}({field.dataType})
{ const nextFields = produce(fields, draft => { diff --git a/packages/graphic-walker/src/insights.ts b/packages/graphic-walker/src/insights.ts index 151ea297..47046182 100644 --- a/packages/graphic-walker/src/insights.ts +++ b/packages/graphic-walker/src/insights.ts @@ -1,4 +1,4 @@ -import { Insight } from 'visual-insights'; +import { IMutField, Insight } from 'visual-insights'; import { Record, IMeasure } from './interfaces'; import { checkMajorFactor, filterByPredicates, checkChildOutlier, IPredicate } from './utils'; import { normalizeWithParent, compareDistribution, normalizeByMeasures, getDistributionDifference } from './utils/normalization'; @@ -19,45 +19,35 @@ export interface IMeasureWithStat extends IMeasure { } export class DataExplainer { public dataSource: Record[]; - private dimensions: string[]; - private measures: string[]; private engine: Insight.VIEngine; private defaultAggs: StatFuncName[] = ['min', 'max', 'sum', 'count', 'mean']; constructor (dataSource: Record[] = []) { this.engine = new Insight.VIEngine(); this.dataSource = dataSource; - this.dimensions = []; - this.measures = []; let keys: string[] = []; if (dataSource.length > 0) { keys = Object.keys(dataSource[0]); } - this.engine.setDataSource(dataSource) - .setFieldKeys(keys) - .buildfieldsSummary(); + this.engine.setData(dataSource) + // .setFieldKeys(keys) + // .buildfieldsSummary(); // const newKeys = this.engine.fields.filter(f => f.domain.size < 40).map(f => f.key); // this.engine.setFieldKeys(keys); // const keys = Object.keys(dataSource[0]) } - public setDimensions (dimensions: string[]) { - this.dimensions = dimensions; - this.engine.setDimensions(dimensions); - return this; - } - public setMeasures (measures: string[]) { - this.measures = measures; - this.engine.setMeasures(measures); - return this; + public setFields(fields: IMutField[]) { + this.engine.setFields(fields); + this.engine.univarSelection(); } public preAnalysis() { - console.log('start') + console.log('[graphic-walker:preAnalysis]start') this.engine.buildGraph(); this.engine.dataGraph.DIMENSION_CORRELATION_THRESHOLD = 0.6; this.engine.dataGraph.MEASURE_CORRELATION_THRESHOLD = 0.8; - console.log('graph finish') + console.log('[graphic-walker:preAnalysis]graph finish') this.engine .clusterFields(); - console.log('cluster finish') + console.log('[graphic-walker:preAnalysis]cluster finish') this.engine.buildSubspaces({ MAX: 2, MIN: 1 @@ -67,9 +57,9 @@ export class DataExplainer { MIN: 1 } ); - console.log('subspaces finsh. start build-cube') + console.log('[graphic-walker:preAnalysis]subspaces finsh. start build-cube') this.engine.buildCube(); - console.log('cube finish') + console.log('[graphic-walker:preAnalysis]cube finish') return this; } public explain (predicates: IPredicate[], dimensions: string[], measures: IMeasure[], threshold: number = 0.3): IExplaination[] { diff --git a/packages/graphic-walker/src/interfaces.ts b/packages/graphic-walker/src/interfaces.ts index ac8ba926..3a43b1b1 100644 --- a/packages/graphic-walker/src/interfaces.ts +++ b/packages/graphic-walker/src/interfaces.ts @@ -1,6 +1,6 @@ import { StatFuncName } from "visual-insights/build/esm/statistics"; import { AggFC } from 'cube-core/built/types'; - +import { IMutField as VIMutField } from 'visual-insights'; export interface Record { [key: string]: any; } @@ -17,6 +17,8 @@ export interface IField { analyticType: 'dimension' | 'measure'; } +export type IMutField = VIMutField; + export interface Field { /** * id: key in data record @@ -45,7 +47,7 @@ export interface Measure extends Field { export interface DataSet { id: string; name: string; - fields: IField[]; + rawFields: IMutField[]; dataSource: Record[]; } diff --git a/packages/graphic-walker/src/services.ts b/packages/graphic-walker/src/services.ts index 3e92710d..80255127 100644 --- a/packages/graphic-walker/src/services.ts +++ b/packages/graphic-walker/src/services.ts @@ -1,4 +1,4 @@ -import { Record, Filters, SemanticType, IMeasure } from './interfaces'; +import { Record, Filters, SemanticType, IMeasure, IMutField } from './interfaces'; // import { Insight } from 'visual-insights'; /* eslint import/no-webpack-loader-syntax:0 */ // @ts-ignore @@ -8,7 +8,7 @@ import { Record, Filters, SemanticType, IMeasure } from './interfaces'; // @ts-ignore // eslint-disable-next-line import ExplainerWorker from './workers/explainer.worker?worker&inline'; -import { View, Specification } from 'visual-insights/build/esm/commonTypes'; +import { View, Specification } from 'visual-insights'; import { IExplaination, IMeasureWithStat } from './insights'; interface WorkerState { @@ -79,8 +79,7 @@ export async function getExplaination(props: ExplainParams) { } interface PreAnalysisParams { - dimensions: string[]; - measures: string[]; + fields: IMutField[]; dataSource: Record[]; } export async function preAnalysis(props: PreAnalysisParams) { diff --git a/packages/graphic-walker/src/workers/explainer.worker.js b/packages/graphic-walker/src/workers/explainer.worker.js index 7eaedd60..888f555f 100644 --- a/packages/graphic-walker/src/workers/explainer.worker.js +++ b/packages/graphic-walker/src/workers/explainer.worker.js @@ -8,9 +8,10 @@ const state = { }; function preAnalysis (props) { - const { dimensions, measures, dataSource } = props; // as ReqData; + const { fields, dataSource } = props; // as ReqData; const de = new DataExplainer(dataSource); - de.setDimensions(dimensions).setMeasures(measures).preAnalysis(); + de.setFields(fields); + de.preAnalysis(); state.de = de; return true } diff --git a/yarn.lock b/yarn.lock index c5655224..34d1d845 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13451,10 +13451,10 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -visual-insights@0.6.2: - version "0.6.2" - resolved "https://registry.nlark.com/visual-insights/download/visual-insights-0.6.2.tgz?cache=0&sync_timestamp=1631964426593&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fvisual-insights%2Fdownload%2Fvisual-insights-0.6.2.tgz#590a7339192c2b79ee6dcf4f6db06b1d505829db" - integrity sha1-WQpzORksK3nubc9PbbBrHVBYKds= +visual-insights@0.6.3: + version "0.6.3" + resolved "https://registry.nlark.com/visual-insights/download/visual-insights-0.6.3.tgz#ed398cf2b26946d03b49aea4c48ea857f6abddeb" + integrity sha1-7TmM8rJpRtA7Sa6kxI6oV/ar3es= dependencies: assert "^2.0.0" cube-core "^2.13.0" From 055c0cb51e76aa05aca167356ac7d088d4e047fc Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Wed, 22 Sep 2021 16:52:22 +0800 Subject: [PATCH 02/10] fix: update graphic-walker in rath --- .../frontend/src/pages/visualInterface/index.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/pages/visualInterface/index.tsx b/packages/frontend/src/pages/visualInterface/index.tsx index ebe4bf06..287173d4 100644 --- a/packages/frontend/src/pages/visualInterface/index.tsx +++ b/packages/frontend/src/pages/visualInterface/index.tsx @@ -2,12 +2,24 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; import { GraphicWalker } from 'graphic-walker'; import { useGlobalStore } from '../../store'; +import { useMemo } from 'react'; +import { IMutField } from 'graphic-walker/dist/interfaces'; const VisualInterface: React.FC = props => { const { dataSourceStore } = useGlobalStore(); // TODO: discuss use clean data from dataSourceStore or cooked data from dataPipeline? - const { cleanedData, dimensions, measures } = dataSourceStore; - return + const { cleanedData, mutFields } = dataSourceStore; + const gwRawFields = useMemo(() => { + return mutFields.map(f => { + return { + key: f.fid, + semanticType: f.semanticType, + dataType: '?', + analyticType: f.analyticType + } + }) + }, [mutFields]) + return } export default observer(VisualInterface); \ No newline at end of file From 9f0890b0668661ee038ac0e0b2a81936f0989aae Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Thu, 23 Sep 2021 01:27:18 +0800 Subject: [PATCH 03/10] refactor: graphic walker state management -> mobx --- packages/graphic-walker/package.json | 2 + packages/graphic-walker/src/App.tsx | 105 +++------- .../graphic-walker/src/dataSource/index.tsx | 194 ++++-------------- .../graphic-walker/src/dataSource/pannel.tsx | 69 +++++++ .../graphic-walker/src/dataSource/table.tsx | 22 +- .../graphic-walker/src/dataSource/utils.ts | 48 +++++ packages/graphic-walker/src/index.tsx | 8 +- packages/graphic-walker/src/interfaces.ts | 20 ++ packages/graphic-walker/src/store/index.tsx | 178 +++++++++++++--- yarn.lock | 10 + 10 files changed, 375 insertions(+), 281 deletions(-) create mode 100644 packages/graphic-walker/src/dataSource/pannel.tsx create mode 100644 packages/graphic-walker/src/dataSource/utils.ts diff --git a/packages/graphic-walker/package.json b/packages/graphic-walker/package.json index a1e5bd7f..51627bc7 100644 --- a/packages/graphic-walker/package.json +++ b/packages/graphic-walker/package.json @@ -22,6 +22,8 @@ "dependencies": { "@fluentui/react": "^8.29.0", "@tableau/tableau-ui": "^3.2.0", + "mobx": "^6.3.3", + "mobx-react-lite": "^3.2.1", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.2", diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index 35d6dc9a..08a91b1a 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -9,11 +9,11 @@ import ClickMenu from './components/clickMenu'; import InsightBoard from './InsightBoard'; import { useFieldsState } from './Fields/useFieldsState' import { Button, DropdownSelect, Checkbox } from '@tableau/tableau-ui'; -import { ProgressIndicator } from '@fluentui/react' import Modal from './components/modal'; -import DataSourcePanel from './dataSource/index'; -import { useLocalState } from './store'; +import DataSourceSegment from './dataSource/index'; +import { useGlobalStore } from './store'; import { preAnalysis, destroyWorker } from './services' +import { observer } from 'mobx-react-lite'; export interface EditorProps { dataSource?: Record[]; @@ -22,7 +22,7 @@ export interface EditorProps { const App: React.FC = props => { const { dataSource = [], rawFields = [] } = props; - const [GS, updateGS] = useLocalState(); + const store = useGlobalStore(); const [fields, setFields] = useState([]); const [geomType, setGeomType] = useState(GEMO_TYPES[0].value); const [aggregated, setAggregated] = useState(true); @@ -30,29 +30,26 @@ const App: React.FC = props => { const [showMenu, setShowMenu] = useState(false); const [showInsight, setShowInsight] = useState(false); const [filters, setFilters] = useState({}); - const [showDSPanel, setShowDSPanel] = useState(false); const [insightReady, setInsightReady] = useState(true); - const [newDBIndex, setNewDBIndex] = useState(0); const { fstate, setFstate, viewDimensions, viewMeasures } = useFieldsState(); + const { currentDataset, datasets } = store; + // use as an embeding module, use outside datasource from props. useEffect(() => { if (dataSource.length > 0) { - updateGS(state => { - state.dataBase = [ - { - id: 'default', - name: 'context dataset', - dataSource: dataSource, - rawFields - } - ] + store.addAndUseDS({ + name: 'context dataset', + dataSource: dataSource, + rawFields }) } }, [dataSource, rawFields]) + + // change selected dataset, update fields, ... useEffect(() => { const fs: Field[] = []; - const ds = GS.dataBase[GS.currentDBIndex]; + const ds = currentDataset; if (ds) { ds.rawFields.forEach((f) => { fs.push({ @@ -64,11 +61,12 @@ const App: React.FC = props => { }) setFields(fs) } - }, [GS.currentDBIndex, GS.dataBase]); + }, [currentDataset]); + // do preparation analysis work when using a new dataset useEffect(() => { - const ds = GS.dataBase[GS.currentDBIndex]; - if (ds) { + const ds = currentDataset; + if (ds && ds.dataSource.length > 0 && ds.rawFields.length > 0) { setInsightReady(false) preAnalysis({ dataSource: ds.dataSource, @@ -80,68 +78,11 @@ const App: React.FC = props => { return () => { destroyWorker(); } - }, [GS.currentDBIndex, GS.dataBase]); - - const createDB = useCallback(() => { - updateGS(draft => { - const newLastIndex = draft.dataBase.length; - draft.dataBase.push({ - id: 'ds_' + newLastIndex, - name: '新数据源' + newLastIndex, - dataSource: [], - rawFields: [] - }) - setNewDBIndex(newLastIndex); - }) - }, []); + }, [currentDataset]); return (
- - {!insightReady && } - - { - // setDSKey(e.target.value); - updateGS((draft) => { - const index = draft.dataBase.findIndex((ds) => ds.id === e.target.value) - draft.currentDBIndex = index - }) - }} - > - {GS.dataBase.map((ds) => ( - - ))} - - - {showDSPanel && ( - { - setShowDSPanel(false) - }} - > - { - setShowDSPanel(false) - }} - /> - - )} - {insightReady && iready} - + { @@ -178,7 +119,7 @@ const App: React.FC = props => {
- {GS.dataBase[GS.currentDBIndex] && ( + {datasets.length > 0 && ( {showInsight && ( = props => { }} > = props => { = props => { ) } -export default App; +export default observer(App); diff --git a/packages/graphic-walker/src/dataSource/index.tsx b/packages/graphic-walker/src/dataSource/index.tsx index 57f98570..204b98d9 100644 --- a/packages/graphic-walker/src/dataSource/index.tsx +++ b/packages/graphic-walker/src/dataSource/index.tsx @@ -1,157 +1,51 @@ -import React, { useRef, useState, useCallback } from 'react'; -import { Button, TextField } from '@tableau/tableau-ui'; -import { FileReader } from '@kanaries/web-data-loader'; -import { Record, IField, IMutField } from '../interfaces'; -import { Insight } from 'visual-insights'; -import Table from './table'; -import styled from 'styled-components'; -import { useLocalState } from '../store'; +import { ProgressIndicator } from '@fluentui/react'; +import { Button, DropdownSelect } from '@tableau/tableau-ui'; +import { observer } from 'mobx-react-lite'; +import React, { useState } from 'react'; +import { Container } from '../components/container'; +import Modal from '../components/modal'; +import DataSourcePanel from './pannel'; +import { useGlobalStore } from '../store'; -const Container = styled.div` - overflow-x: auto; - table { - box-sizing: content-box; - font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; - border-collapse: collapse; - thead { - td { - text-align: left; - } - } - tbody { - td.number { - text-align: right; - } - td.text { - text-align: left; - } - } - } -`; -function transData(dataSource: Record[]): { - dataSource: Record[]; - fields: IMutField[] -} { - if (dataSource.length === 0) return { - dataSource: [], - fields: [] - }; - let ans: Record[] = []; - const keys = Object.keys(dataSource[0]); - // TODO: 冗余设计,单变量统计被进行了多次重复计算。另外对于这种不完整的分析任务,不建议使用VIEngine。 - const vie = new Insight.VIEngine(); - vie.setData(dataSource) - .setFields(keys.map(k => ({ - key: k, - analyticType: '?', - dataType: '?', - semanticType: '?' - }))) - // TODO: 结合上面的TODO,讨论,VIEngine是否要提供不需要进行univarSelection就提供summary的接口。 - // 这里我们使用了一种非原API设计时期待的用法,即强制指定单变量选择时要全选字段。但我们无法阻止对变量的转换。 - vie.univarSelection('percent', 1); - const fields = vie.fields; - // console.log(fields) - for (let record of dataSource) { - const newRecord: Record = {}; - for (let field of fields) { - if (field.dataType === 'number' || field.dataType === 'integer') { - newRecord[field.key] = Number(record[field.key]) - } else { - newRecord[field.key] = record[field.key] - } - } - ans.push(newRecord); - } - return { - dataSource: ans, - fields: fields.map(f => ({ - key: f.key, - analyticType: f.analyticType, - dataType: f.dataType, - semanticType: f.semanticType - })) - } +interface DSSegmentProps { + preWorkDone: boolean; } -interface DSPanelProps { - dbIndex: number; - onSubmit?: () => void -} -const DataSourcePanel: React.FC = props => { - const { dbIndex, onSubmit } = props; - const fileRef = useRef(null); - const [dataSource, setDataSource] = useState([]); - const [rawFields, setRawFields] = useState([]); - const [, updateGS] = useLocalState(); - const [dsName, setDSName] = useState('新数据集') - - const onFieldsChange = useCallback((fields: IMutField[]) => { - setRawFields(fields); - }, []) +const DataSourceSegment: React.FC = props => { + const { preWorkDone } = props; + const store = useGlobalStore(); + const { currentDataset, datasets, showDSPanel } = store; - const onSubmitData = useCallback(() => { - updateGS(draft => { - draft.dataBase[dbIndex] = { - id: 'test' + dbIndex, - name: dsName, - rawFields, - dataSource - } - }) - if (onSubmit) { - onSubmit(); - } - }, [dbIndex, rawFields, dataSource, dsName]) - return ( - - { - const files = e.target.files; - if (files !== null) { - const file = files[0]; - FileReader.csvReader({ - file, - config: { type: 'reservoirSampling', size: Infinity }, - onLoading: () => {} - }).then((data) => { - // console.log(data) - const result = transData(data as Record[]); - console.log(result); - // TODO: need fix web-data-loader issue #2 - setDataSource(result.dataSource.slice(0, -1)); - setRawFields(result.fields); - }); - } - }} - /> -
- - -
-
- { - setDSName(e.target.value) - }} /> -
- - - ); + return + {!preWorkDone && } + + { store.useDS(e.target.value); }} + > + {datasets.map((ds) => ( + + ))} + + + {showDSPanel && ( + { store.setShowDSPanel(false) }} + > + + + )} + {preWorkDone && iready} + } -export default DataSourcePanel; +export default observer(DataSourceSegment); \ No newline at end of file diff --git a/packages/graphic-walker/src/dataSource/pannel.tsx b/packages/graphic-walker/src/dataSource/pannel.tsx new file mode 100644 index 00000000..0fc9f3c8 --- /dev/null +++ b/packages/graphic-walker/src/dataSource/pannel.tsx @@ -0,0 +1,69 @@ +import React, { useRef, useState, useCallback } from 'react'; +import { Button, TextField } from '@tableau/tableau-ui'; +import { FileReader } from '@kanaries/web-data-loader'; +import { Record } from '../interfaces'; +import Table from './table'; +import styled from 'styled-components'; +import { useGlobalStore } from '../store'; +import { observer } from 'mobx-react-lite'; + +const Container = styled.div` + overflow-x: auto; +`; + +interface DSPanelProps { +} +const DataSourcePanel: React.FC = props => { + const fileRef = useRef(null); + const store = useGlobalStore(); + const { tmpDSName, tmpDataSource } = store; + + const onSubmitData = useCallback(() => { + store.commitTempDS(); + }, []) + return ( + + { + const files = e.target.files; + if (files !== null) { + const file = files[0]; + FileReader.csvReader({ + file, + config: { type: 'reservoirSampling', size: Infinity }, + onLoading: () => {} + }).then((data) => { + store.updateTempDS(data as Record[]); + }); + } + }} + /> +
+ + +
+
+ { + store.updateTempName(e.target.value) + }} /> +
+
+ + ); +} + +export default observer(DataSourcePanel); diff --git a/packages/graphic-walker/src/dataSource/table.tsx b/packages/graphic-walker/src/dataSource/table.tsx index 3a874d5a..fb87ea17 100644 --- a/packages/graphic-walker/src/dataSource/table.tsx +++ b/packages/graphic-walker/src/dataSource/table.tsx @@ -3,12 +3,11 @@ import { Record, IField, IMutField } from '../interfaces'; import styled from 'styled-components'; import { DropdownSelect } from '@tableau/tableau-ui'; import produce from 'immer'; +import { observer } from 'mobx-react-lite'; +import { useGlobalStore } from '../store'; interface TableProps { - fields: IMutField[]; - dataSource: Record[]; size?: number; - onFieldsUpdate: (fields: IMutField[]) => void } const Container = styled.div` overflow-x: auto; @@ -62,21 +61,20 @@ function getHeaderType(field: IMutField): 'number' | 'text' { return field.analyticType === 'dimension'? 'text' : 'number'; } const Table: React.FC = props => { - const { fields, dataSource, size = 10, onFieldsUpdate } = props; + const { size = 10 } = props; + const store = useGlobalStore(); + const { tmpDSRawFields, tmpDataSource } = store; return (
- {fields.map((field, fIndex) => ( + {tmpDSRawFields.map((field, fIndex) => ( - {dataSource.slice(0, size).map((record, index) => ( + {tmpDataSource.slice(0, size).map((record, index) => ( - {fields.map((field) => ( + {tmpDSRawFields.map((field) => (
{field.key}({field.dataType})
{ - const nextFields = produce(fields, draft => { - draft[fIndex].analyticType = e.target.value as any; - }) - onFieldsUpdate(nextFields); + store.updateTempFieldAnalyticType(field.key, e.target.value as IMutField['analyticType']) }}> { TYPE_LIST.map(type => ) @@ -89,9 +87,9 @@ const Table: React.FC = props => {
= props => { ); } -export default Table; +export default observer(Table); diff --git a/packages/graphic-walker/src/dataSource/utils.ts b/packages/graphic-walker/src/dataSource/utils.ts new file mode 100644 index 00000000..6fbcbb5b --- /dev/null +++ b/packages/graphic-walker/src/dataSource/utils.ts @@ -0,0 +1,48 @@ +import { Record, IField, IMutField } from '../interfaces'; +import { Insight } from 'visual-insights'; + +export function transData(dataSource: Record[]): { + dataSource: Record[]; + fields: IMutField[] +} { + if (dataSource.length === 0) return { + dataSource: [], + fields: [] + }; + let ans: Record[] = []; + const keys = Object.keys(dataSource[0]); + // TODO: 冗余设计,单变量统计被进行了多次重复计算。另外对于这种不完整的分析任务,不建议使用VIEngine。 + const vie = new Insight.VIEngine(); + vie.setData(dataSource) + .setFields(keys.map(k => ({ + key: k, + analyticType: '?', + dataType: '?', + semanticType: '?' + }))) + // TODO: 结合上面的TODO,讨论,VIEngine是否要提供不需要进行univarSelection就提供summary的接口。 + // 这里我们使用了一种非原API设计时期待的用法,即强制指定单变量选择时要全选字段。但我们无法阻止对变量的转换。 + vie.univarSelection('percent', 1); + const fields = vie.fields; + // console.log(fields) + for (let record of dataSource) { + const newRecord: Record = {}; + for (let field of fields) { + if (field.dataType === 'number' || field.dataType === 'integer') { + newRecord[field.key] = Number(record[field.key]) + } else { + newRecord[field.key] = record[field.key] + } + } + ans.push(newRecord); + } + return { + dataSource: ans, + fields: fields.map(f => ({ + key: f.key, + analyticType: f.analyticType, + dataType: f.dataType, + semanticType: f.semanticType + })) + } +} \ No newline at end of file diff --git a/packages/graphic-walker/src/index.tsx b/packages/graphic-walker/src/index.tsx index 2367453b..c5c30d97 100644 --- a/packages/graphic-walker/src/index.tsx +++ b/packages/graphic-walker/src/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import App, { EditorProps } from './App'; -import { GlobalContextWrapper } from './store/index'; +import { StoreWrapper } from './store/index'; export const GraphicWalker: React.FC = props => { - return - - + return + + } diff --git a/packages/graphic-walker/src/interfaces.ts b/packages/graphic-walker/src/interfaces.ts index 3a43b1b1..825dd6cc 100644 --- a/packages/graphic-walker/src/interfaces.ts +++ b/packages/graphic-walker/src/interfaces.ts @@ -59,4 +59,24 @@ export interface IFieldNeighbor { export interface IMeasure { key: string; op: StatFuncName +} + +export interface IDataSet { + id: string; + name: string; + rawFields: IMutField[]; + dsId: string; +} +/** + * use as props to create a new dataset(IDataSet). + */ +export interface IDataSetInfo { + name: string; + rawFields: IMutField[]; + dataSource: Record[] +} + +export interface IDataSource { + id: string; + data: Record[] } \ No newline at end of file diff --git a/packages/graphic-walker/src/store/index.tsx b/packages/graphic-walker/src/store/index.tsx index 983e2aeb..db285087 100644 --- a/packages/graphic-walker/src/store/index.tsx +++ b/packages/graphic-walker/src/store/index.tsx @@ -1,38 +1,150 @@ -import React, { useContext, useState, useCallback } from 'react'; -import { DataSet } from '../interfaces'; -import produce, { setAutoFreeze } from 'immer'; -setAutoFreeze(false); -interface IGlobalState { - dataBase: DataSet[]; - currentDBIndex: number; -} +import React, { useContext } from 'react'; +import { DataSet, IDataSet, IDataSetInfo, IDataSource, IMutField, Record } from '../interfaces'; -export function getInitGState(): IGlobalState { - return { - dataBase: [ - // getStudentsData(), - // getTitanicData() - ], - currentDBIndex: 0, - }; -} -type IUpdater = (draft: T) => void; -const initState = getInitGState(); -export const GlobalContext = React.createContext<[IGlobalState, (updater: IUpdater) => void]>(null!); +import { makeAutoObservable, observable } from 'mobx'; +import { useEffect } from 'react'; +import { transData } from '../dataSource/utils'; +class GlobalStore { + public datasets: IDataSet[] = []; + public dataSources: IDataSource[] = []; + public dsIndex: number = 0; + public tmpDSName: string = ''; + public tmpDSRawFields: IMutField[] = []; + public tmpDataSource: Record[] = []; + public showDSPanel: boolean = false; + constructor () { + this.datasets = []; + this.dataSources = []; + makeAutoObservable(this, { + dataSources: observable.ref, + tmpDataSource: observable.ref + }); + } + public get currentDataset (): DataSet { + const datasetIndex = this.dsIndex; + if (this.datasets.length > 0) { + const dataSourceId = this.datasets[datasetIndex].dsId; + const dataSource = this.dataSources.find(d => d.id === dataSourceId); + return { + ...this.datasets[datasetIndex], + dataSource: dataSource ? dataSource.data : [] + } + } + return { + id: '__null_ds__', + name: 'Empty Dataset', + rawFields: [], + dataSource: [] + } + } + public setShowDSPanel (show: boolean) { + this.showDSPanel = show; + } + public initTempDS () { + this.tmpDSName = 'New Dataset' + this.tmpDSRawFields = []; + this.tmpDataSource = []; + } + public updateTempFields (fields: IMutField[]) { + this.tmpDSRawFields = fields; + } + + public updateTempFieldAnalyticType (fieldKey: string, analyticType: IMutField['analyticType']) { + const field = this.tmpDSRawFields.find(f => f.key === fieldKey); + if (field) { + field.analyticType = analyticType; + } + } + + public updateTempName (name: string) { + this.tmpDSName = name; + } + + public updateTempDS (rawData: Record[]) { + const result = transData(rawData); + // TODO: need fix web-data-loader issue #2 + this.tmpDataSource = result.dataSource.slice(0, -1); + this.tmpDSRawFields = result.fields; + } -export function useLocalState() { - return useContext(GlobalContext); + public commitTempDS () { + const { tmpDSName, tmpDSRawFields, tmpDataSource } = this; + this.addAndUseDS({ + dataSource: tmpDataSource, + rawFields: tmpDSRawFields, + name: tmpDSName + }) + this.setShowDSPanel(false); + this.initTempDS(); + } + + public startDSBuildingTask () { + this.initTempDS(); + this.showDSPanel = true; + } + public addAndUseDS(dataset: IDataSetInfo) { + const datasetId = this.addDS(dataset); + this.dsIndex = this.datasets.length - 1; + return datasetId + } + public addDS(dataset: IDataSetInfo) { + const timestamp = new Date().getTime(); + const dataSetId = `dst-${timestamp}` + const dataSourceId = `dse-${timestamp}`; + this.dataSources.push({ + id: dataSourceId, + data: dataset.dataSource + }) + this.datasets.push({ + id: dataSetId, + name: dataset.name, + rawFields: dataset.rawFields, + dsId: dataSourceId + }) + return dataSetId; + } + public removeDS(datasetId: string) { + const datasetIndex = this.datasets.findIndex(d => d.id === datasetId); + if (datasetIndex > -1) { + const dataSourceId = this.datasets[datasetIndex].dsId; + const dataSourceIndex = this.dataSources.findIndex(d => d.id === dataSourceId); + this.dataSources.splice(dataSourceIndex, 1); + this.datasets.splice(datasetIndex, 1); + } + } + public useDS(datasetId: string) { + const datasetIndex = this.datasets.findIndex(d => d.id === datasetId); + if (datasetIndex > -1) { + this.dsIndex = datasetIndex; + } + } + public createPlaceholderDS() { + this.addDS({ + name: '新数据源', + dataSource: [], + rawFields: [] + }) + } + public destroy () { + this.dataSources = []; + this.datasets = []; + } } -export const GlobalContextWrapper: React.FC = props => { - const [gs, setGS] = useState(initState); - const updateGlobalState = useCallback((updater: IUpdater) => { - const nextState = produce(gs, updater); - setGS(nextState); - }, [gs]) - return - { - props.children +const initStore = new GlobalStore(); +const StoreContext = React.createContext(initStore); + +export const StoreWrapper: React.FC = props => { + useEffect(() => { + return () => { + initStore.destroy(); } - -} \ No newline at end of file + }, []) + return + { props.children } + +} + +export function useGlobalStore() { + return useContext(StoreContext); +} diff --git a/yarn.lock b/yarn.lock index 34d1d845..ba36b90b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8805,6 +8805,11 @@ mobx-react-lite@^3.2.0: resolved "https://registry.npm.taobao.org/mobx-react-lite/download/mobx-react-lite-3.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmobx-react-lite%2Fdownload%2Fmobx-react-lite-3.2.0.tgz#331d7365a6b053378dfe9c087315b4e41c5df69f" integrity sha1-Mx1zZaawUzeN/pwIcxW05Bxd9p8= +mobx-react-lite@^3.2.1: + version "3.2.1" + resolved "https://registry.nlark.com/mobx-react-lite/download/mobx-react-lite-3.2.1.tgz#c549a3722f1eae51a78b12f839311614d5689c58" + integrity sha1-xUmjci8erlGnixL4OTEWFNVonFg= + mobx-utils@^6.0.4: version "6.0.4" resolved "https://registry.npm.taobao.org/mobx-utils/download/mobx-utils-6.0.4.tgz#5283a466ece8de0ac36ae3cfa1b1c032ec302b37" @@ -8815,6 +8820,11 @@ mobx@^6.3.2: resolved "https://registry.nlark.com/mobx/download/mobx-6.3.2.tgz#125590961f702a572c139ab69392bea416d2e51b" integrity sha1-ElWQlh9wKlcsE5q2k5K+pBbS5Rs= +mobx@^6.3.3: + version "6.3.3" + resolved "https://registry.nlark.com/mobx/download/mobx-6.3.3.tgz#a3006c56243b1c7ea4ee671a66f963b9f43cf1af" + integrity sha1-owBsViQ7HH6k7mcaZvljufQ88a8= + mocha@^6.2.0: version "6.2.3" resolved "https://registry.nlark.com/mocha/download/mocha-6.2.3.tgz#e648432181d8b99393410212664450a4c1e31912" From a7864bb472a8e1f109053691612fa7c935a6f78a Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Thu, 23 Sep 2021 01:42:44 +0800 Subject: [PATCH 04/10] doc: readme for graphic walker --- README.md | 2 ++ README.zh-CN.md | 2 ++ packages/graphic-walker/README.md | 32 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 packages/graphic-walker/README.md diff --git a/README.md b/README.md index 13d714cf..8fad9c33 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ Graphic Walker is a lite tableau style visual analysis interface. It is used for You can also use Graphic Walker as a lite tableau style analysis app independently. It can be used as an independent app or an embeding module. +more details can be found in README.md in graphic-walker folder. + ## Examples + [DataSet: NASA - Kepler](https://www.kaggle.com/nasa/kepler-exoplanet-search-results) diff --git a/README.zh-CN.md b/README.zh-CN.md index e15713b1..43ac9e81 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -33,6 +33,8 @@ Rath中包含一个tableau风格的自助分析工具,它是一个和基于图 这个模块从工程上是独立的,你可以把它单独作为一个分析应用来使用或者作为一个嵌入式的模块(Rath便是这也使用它)。 +详细的使用方式详见graphic-walker文件夹下的README.md文件 + ## 案例 diff --git a/packages/graphic-walker/README.md b/packages/graphic-walker/README.md new file mode 100644 index 00000000..b0a70a43 --- /dev/null +++ b/packages/graphic-walker/README.md @@ -0,0 +1,32 @@ +# Graphic Walker +Graphic Walker is a lite tableau style visual analysis interface. It is used for cases when users have specific analytic target or user want to analysis further result based on the recommanded results by Rath's auto insights. + +** You can also use Graphic Walker as a lite tableau style analysis app independently. It can be used as an independent app or an embeding module. ** + +Main features: + ++ A grammar of graphics based visual analytic user interface where use can build visualization from low level visual channel encodings. ++ A Data Explainer which explain some why some patterns occur / what may cause them. + +## Usage +```bash +cd graphic-waler +yarn install +npm run build +``` + +In your app: +```typescript +import { GraphicWalker } from 'graphic-walker'; + +const YourEmbeddingTableauStyleApp: React.FC = props => { + const { dataSource, fields } = props; + + return +} + +export default YourEmbeddingTableauStyleApp; +``` \ No newline at end of file From 23201e45ba0087199022c76daf91e81ce03a7a06 Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Thu, 23 Sep 2021 10:30:19 +0800 Subject: [PATCH 05/10] feat: tailwind config in graphic-walker --- packages/graphic-walker/package.json | 3 + packages/graphic-walker/postcss.config.js | 6 + packages/graphic-walker/src/index.tsx | 2 + packages/graphic-walker/src/main.tsx | 1 - packages/graphic-walker/tailwind.config.js | 13 + yarn.lock | 350 ++++++++++++++++++++- 6 files changed, 365 insertions(+), 10 deletions(-) create mode 100644 packages/graphic-walker/postcss.config.js create mode 100644 packages/graphic-walker/tailwind.config.js diff --git a/packages/graphic-walker/package.json b/packages/graphic-walker/package.json index 51627bc7..d57151ce 100644 --- a/packages/graphic-walker/package.json +++ b/packages/graphic-walker/package.json @@ -22,13 +22,16 @@ "dependencies": { "@fluentui/react": "^8.29.0", "@tableau/tableau-ui": "^3.2.0", + "autoprefixer": "^10.3.5", "mobx": "^6.3.3", "mobx-react-lite": "^3.2.1", + "postcss": "^8.3.7", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.0", "react-dom": "^17.0.2", "react-json-view": "^1.21.3", "styled-components": "^5.3.0", + "tailwindcss": "^2.2.15", "vega": "^5.20.2", "vega-embed": "^6.18.2", "vega-lite": "^5.1.0", diff --git a/packages/graphic-walker/postcss.config.js b/packages/graphic-walker/postcss.config.js new file mode 100644 index 00000000..f7bd3abe --- /dev/null +++ b/packages/graphic-walker/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + } +} \ No newline at end of file diff --git a/packages/graphic-walker/src/index.tsx b/packages/graphic-walker/src/index.tsx index c5c30d97..3b8e3eb2 100644 --- a/packages/graphic-walker/src/index.tsx +++ b/packages/graphic-walker/src/index.tsx @@ -1,6 +1,8 @@ import React from 'react'; import App, { EditorProps } from './App'; import { StoreWrapper } from './store/index'; +import "tailwindcss/tailwind.css" +import './index.css' export const GraphicWalker: React.FC = props => { return diff --git a/packages/graphic-walker/src/main.tsx b/packages/graphic-walker/src/main.tsx index df741c34..04758a0d 100644 --- a/packages/graphic-walker/src/main.tsx +++ b/packages/graphic-walker/src/main.tsx @@ -1,6 +1,5 @@ import React from 'react' import ReactDOM from 'react-dom' -import './index.css' import { GraphicWalker } from './index' ReactDOM.render( diff --git a/packages/graphic-walker/tailwind.config.js b/packages/graphic-walker/tailwind.config.js new file mode 100644 index 00000000..18cff63d --- /dev/null +++ b/packages/graphic-walker/tailwind.config.js @@ -0,0 +1,13 @@ +module.exports = { + purge: [ + "./src/**/*.tsx" + ], + darkMode: false, // or 'media' or 'class' + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], +} diff --git a/yarn.lock b/yarn.lock index ba36b90b..c70a1390 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2826,7 +2826,16 @@ acorn-jsx@^5.3.1: resolved "https://registry.nlark.com/acorn-jsx/download/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha1-/IZh4Rt6wVOcR9v+oucrOvNNJns= -acorn-walk@^7.1.1: +acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.npm.taobao.org/acorn-node/download/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha1-EUyV1kU55T3t4j3oudlt98euKvg= + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0, acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.nlark.com/acorn-walk/download/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha1-DeiJpgEgOQmw++B7iTjcIdLpZ7w= @@ -2836,7 +2845,7 @@ acorn@^6.4.1: resolved "https://registry.nlark.com/acorn/download/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha1-NYZv1xBSjpLeEM8GAWSY5H454eY= -acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.0.0, acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.nlark.com/acorn/download/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha1-/q7SVZc9LndVW4PbwIhRpsY1IPo= @@ -3003,6 +3012,11 @@ aproba@^1.1.1: resolved "https://registry.nlark.com/aproba/download/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha1-aALmJk79GMeQobDVF/DyYnvyyUo= +arg@^5.0.1: + version "5.0.1" + resolved "https://registry.nlark.com/arg/download/arg-5.0.1.tgz?cache=0&sync_timestamp=1629166537485&other_urls=https%3A%2F%2Fregistry.nlark.com%2Farg%2Fdownload%2Farg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb" + integrity sha1-6wyaj3d4bK0q+P8rhiiZhC17ats= + argparse@^1.0.7: version "1.0.10" resolved "https://registry.nlark.com/argparse/download/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -3202,6 +3216,18 @@ atob@^2.1.2: resolved "https://registry.nlark.com/atob/download/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k= +autoprefixer@^10.3.5: + version "10.3.5" + resolved "https://registry.nlark.com/autoprefixer/download/autoprefixer-10.3.5.tgz?cache=0&sync_timestamp=1632290934335&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fautoprefixer%2Fdownload%2Fautoprefixer-10.3.5.tgz#762e6c13e30c5a0e650bf81d9ffd5713f1c8f344" + integrity sha1-di5sE+MMWg5lC/gdn/1XE/HI80Q= + dependencies: + browserslist "^4.17.1" + caniuse-lite "^1.0.30001259" + fraction.js "^4.1.1" + nanocolors "^0.1.5" + normalize-range "^0.1.2" + postcss-value-parser "^4.1.0" + autoprefixer@^9.6.1: version "9.8.6" resolved "https://registry.nlark.com/autoprefixer/download/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" @@ -3696,6 +3722,17 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.16.6, browserslist@^4 escalade "^3.1.1" node-releases "^1.1.71" +browserslist@^4.17.1: + version "4.17.1" + resolved "https://registry.nlark.com/browserslist/download/browserslist-4.17.1.tgz?cache=0&sync_timestamp=1632288361873&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbrowserslist%2Fdownload%2Fbrowserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9" + integrity sha1-qY0QT1SvRBKQt9WSYm3VQfpkLrk= + dependencies: + caniuse-lite "^1.0.30001259" + electron-to-chromium "^1.3.846" + escalade "^3.1.1" + nanocolors "^0.1.5" + node-releases "^1.1.76" + bser@2.1.1: version "2.1.1" resolved "https://registry.npm.taobao.org/bser/download/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -3742,7 +3779,7 @@ bytes@3.0.0: resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.0: +bytes@3.1.0, bytes@^3.0.0: version "3.1.0" resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY= @@ -3846,6 +3883,11 @@ camel-case@^4.1.1: pascal-case "^3.1.2" tslib "^2.0.3" +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.npm.taobao.org/camelcase-css/download/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha1-7pePaUeRTMMMa0R0G27R338EP9U= + camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -3876,6 +3918,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, can resolved "https://registry.nlark.com/caniuse-lite/download/caniuse-lite-1.0.30001241.tgz#cd3fae47eb3d7691692b406568d7a3e5b23c7598" integrity sha1-zT+uR+s9dpFpK0BlaNej5bI8dZg= +caniuse-lite@^1.0.30001259: + version "1.0.30001259" + resolved "https://registry.nlark.com/caniuse-lite/download/caniuse-lite-1.0.30001259.tgz?cache=0&sync_timestamp=1632203439345&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcaniuse-lite%2Fdownload%2Fcaniuse-lite-1.0.30001259.tgz#ae21691d3da9c4be6144403ac40f71d9f6efd790" + integrity sha1-riFpHT2pxL5hREA6xA9x2fbv15A= + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.nlark.com/capture-exit/download/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -3918,6 +3965,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.nlark.com/chalk/download/chalk-4.1.2.tgz?cache=0&sync_timestamp=1627646655305&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fchalk%2Fdownload%2Fchalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha1-qsTit3NKdAhnrrFr8CqtVWoeegE= + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.nlark.com/char-regex/download/char-regex-1.0.2.tgz?cache=0&sync_timestamp=1622809103243&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fchar-regex%2Fdownload%2Fchar-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -3962,7 +4017,7 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.4.1: +chokidar@^3.4.1, chokidar@^3.5.2: version "3.5.2" resolved "https://registry.nlark.com/chokidar/download/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" integrity sha1-26OXb8rbAW9m/TZQIdkWANAcHnU= @@ -4158,6 +4213,14 @@ color-string@^1.5.4: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-string@^1.6.0: + version "1.6.0" + resolved "https://registry.nlark.com/color-string/download/color-string-1.6.0.tgz?cache=0&sync_timestamp=1626503533872&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcolor-string%2Fdownload%2Fcolor-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha1-w5FfYf4mdnLLfh4GTJ1pIhn2wxI= + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + color@^3.0.0: version "3.1.3" resolved "https://registry.npm.taobao.org/color/download/color-3.1.3.tgz?cache=0&sync_timestamp=1602228725017&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcolor%2Fdownload%2Fcolor-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" @@ -4166,6 +4229,14 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.4" +color@^4.0.1: + version "4.0.1" + resolved "https://registry.nlark.com/color/download/color-4.0.1.tgz?cache=0&sync_timestamp=1628104040071&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcolor%2Fdownload%2Fcolor-4.0.1.tgz#21df44cd10245a91b1ccf5ba031609b0e10e7d67" + integrity sha1-Id9EzRAkWpGxzPW6AxYJsOEOfWc= + dependencies: + color-convert "^2.0.1" + color-string "^1.6.0" + colorette@^1.2.1, colorette@^1.2.2: version "1.2.2" resolved "https://registry.nlark.com/colorette/download/colorette-1.2.2.tgz?cache=0&sync_timestamp=1618846981554&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcolorette%2Fdownload%2Fcolorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" @@ -4188,6 +4259,11 @@ commander@^4.1.1: resolved "https://registry.nlark.com/commander/download/commander-4.1.1.tgz?cache=0&sync_timestamp=1624609539421&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcommander%2Fdownload%2Fcommander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha1-n9YCvZNilOnp70aj9NaWQESxgGg= +commander@^6.0.0: + version "6.2.1" + resolved "https://registry.nlark.com/commander/download/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha1-B5LraC37wyWZm7K4T93duhEKxzw= + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.npm.taobao.org/common-tags/download/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -4408,6 +4484,17 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^7.0.1: + version "7.0.1" + resolved "https://registry.nlark.com/cosmiconfig/download/cosmiconfig-7.0.1.tgz?cache=0&sync_timestamp=1629586119976&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fcosmiconfig%2Fdownload%2Fcosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha1-cU11ZSLKzoZ4Z8y0R0xdAbuuXW0= + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.npm.taobao.org/create-ecdh/download/create-ecdh-4.0.4.tgz?cache=0&sync_timestamp=1596557450797&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcreate-ecdh%2Fdownload%2Fcreate-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -4611,6 +4698,11 @@ css-tree@^1.1.2: mdn-data "2.0.14" source-map "^0.6.1" +css-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.nlark.com/css-unit-converter/download/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha1-THf1oZVObb/2BpXsshTjJwQ2qyE= + css-what@^3.2.1: version "3.4.2" resolved "https://registry.nlark.com/css-what/download/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" @@ -5071,6 +5163,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.npm.taobao.org/defined/download/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + del@^4.1.1: version "4.1.1" resolved "https://registry.npm.taobao.org/del/download/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" @@ -5130,6 +5227,20 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.npm.taobao.org/detective/download/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha1-/rKnfoW5BOzepFmtiXzJCpm9Kns= + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.nlark.com/didyoumean/download/didyoumean-1.2.2.tgz?cache=0&sync_timestamp=1624543452248&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdidyoumean%2Fdownload%2Fdidyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha1-mJNG/+noObRVXs9WZu3qDT6K0Dc= + diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.nlark.com/diff-sequences/download/diff-sequences-26.6.2.tgz?cache=0&sync_timestamp=1624900057366&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdiff-sequences%2Fdownload%2Fdiff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -5156,6 +5267,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.npm.taobao.org/dlv/download/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha1-XBmKihFFNZbnUUlNSYdLx3MvLnk= + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.npm.taobao.org/dns-equal/download/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -5335,6 +5451,11 @@ electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.723: resolved "https://registry.nlark.com/electron-to-chromium/download/electron-to-chromium-1.3.762.tgz#3fa4e3bcbda539b50e3aa23041627063a5cffe61" integrity sha1-P6TjvL2lObUOOqIwQWJwY6XP/mE= +electron-to-chromium@^1.3.846: + version "1.3.848" + resolved "https://registry.npmmirror.com/electron-to-chromium/download/electron-to-chromium-1.3.848.tgz?cache=0&sync_timestamp=1632362659953&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.848.tgz#94cc196e496f33c0d71cd98561448f10018584cc" + integrity sha1-lMwZbklvM8DXHNmFYUSPEAGFhMw= + elliptic@^6.5.3: version "6.5.4" resolved "https://registry.nlark.com/elliptic/download/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -5993,6 +6114,17 @@ fast-glob@^3.1.1: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.7: + version "3.2.7" + resolved "https://registry.nlark.com/fast-glob/download/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha1-/Wy3otfpqnp4RhEehaGW1rL3ZqE= + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-patch@^3.0.0-1: version "3.0.0-1" resolved "https://registry.npm.taobao.org/fast-json-patch/download/fast-json-patch-3.0.0-1.tgz#4c68f2e7acfbab6d29d1719c44be51899c93dabb" @@ -6267,6 +6399,11 @@ forwarded@0.2.0: resolved "https://registry.nlark.com/forwarded/download/forwarded-0.2.0.tgz?cache=0&sync_timestamp=1622503508967&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fforwarded%2Fdownload%2Fforwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha1-ImmTZCiq1MFcfr6XeahL8LKoGBE= +fraction.js@^4.1.1: + version "4.1.1" + resolved "https://registry.nlark.com/fraction.js/download/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" + integrity sha1-rE5SBHPa5nAS1hiquR7aCby0AP8= + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.nlark.com/fragment-cache/download/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -6292,6 +6429,15 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-extra@^10.0.0: + version "10.0.0" + resolved "https://registry.nlark.com/fs-extra/download/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" + integrity sha1-n/YbZV3eU/s0qC34S7IUzoAuF8E= + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.nlark.com/fs-extra/download/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -6450,6 +6596,13 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.1: + version "6.0.1" + resolved "https://registry.nlark.com/glob-parent/download/glob-parent-6.0.1.tgz?cache=0&sync_timestamp=1626760165717&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fglob-parent%2Fdownload%2Fglob-parent-6.0.1.tgz#42054f685eb6a44e7a7d189a96efa40a54971aa7" + integrity sha1-QgVPaF62pE56fRialu+kClSXGqc= + dependencies: + is-glob "^4.0.1" + glob@7.1.3: version "7.1.3" resolved "https://registry.nlark.com/glob/download/glob-7.1.3.tgz?cache=0&sync_timestamp=1620337382269&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fglob%2Fdownload%2Fglob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" @@ -6462,6 +6615,18 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.0.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/glob/download/glob-7.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fglob%2Fdownload%2Fglob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha1-0VU1r3cy4C6Uj0xBYovZECk/YCM= + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.7" resolved "https://registry.nlark.com/glob/download/glob-7.1.7.tgz?cache=0&sync_timestamp=1620337382269&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fglob%2Fdownload%2Fglob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -6749,6 +6914,11 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" +html-tags@^3.1.0: + version "3.1.0" + resolved "https://registry.nlark.com/html-tags/download/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" + integrity sha1-e15vfmZen7QfMAB+2eDUHpf7IUA= + html-to-react@^1.3.4: version "1.4.5" resolved "https://registry.npm.taobao.org/html-to-react/download/html-to-react-1.4.5.tgz#59091c11021d1ef315ef738460abb6a4a41fe1ce" @@ -6966,6 +7136,13 @@ import-cwd@^2.0.0: dependencies: import-from "^2.1.0" +import-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.nlark.com/import-cwd/download/import-cwd-3.0.0.tgz#20845547718015126ea9b3676b7592fb8bd4cf92" + integrity sha1-IIRVR3GAFRJuqbNna3WS+4vUz5I= + dependencies: + import-from "^3.0.0" + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.npm.taobao.org/import-fresh/download/import-fresh-2.0.0.tgz?cache=0&sync_timestamp=1608469579940&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fimport-fresh%2Fdownload%2Fimport-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -6989,6 +7166,13 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" +import-from@^3.0.0: + version "3.0.0" + resolved "https://registry.nlark.com/import-from/download/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha1-BVz+w4zVon2AV8pRN219O/CJGWY= + dependencies: + resolve-from "^5.0.0" + import-local@^2.0.0: version "2.0.0" resolved "https://registry.nlark.com/import-local/download/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -7229,7 +7413,7 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-color-stop@^1.0.0: +is-color-stop@^1.0.0, is-color-stop@^1.1.0: version "1.1.0" resolved "https://registry.nlark.com/is-color-stop/download/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= @@ -8279,6 +8463,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lilconfig@^2.0.3: + version "2.0.3" + resolved "https://registry.nlark.com/lilconfig/download/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" + integrity sha1-aPMAXpIdr70qKvtIN5mGqm0lef0= + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.nlark.com/lines-and-columns/download/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -8404,6 +8593,11 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "^3.0.0" +lodash.topath@^4.5.2: + version "4.5.2" + resolved "https://registry.npm.taobao.org/lodash.topath/download/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009" + integrity sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak= + lodash.truncate@^4.4.2: version "4.4.2" resolved "https://registry.nlark.com/lodash.truncate/download/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" @@ -8854,6 +9048,11 @@ mocha@^6.2.0: yargs-parser "13.1.2" yargs-unparser "1.6.0" +modern-normalize@^1.1.0: + version "1.1.0" + resolved "https://registry.nlark.com/modern-normalize/download/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" + integrity sha1-2o6AFA2SIUJr1PclxuESg9NPkLc= + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.nlark.com/move-concurrently/download/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -8904,11 +9103,21 @@ nan@^2.12.1: resolved "https://registry.nlark.com/nan/download/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha1-9TdkAGlRaPTMaUrJOT0MlYXu6hk= +nanocolors@^0.1.5: + version "0.1.6" + resolved "https://registry.nlark.com/nanocolors/download/nanocolors-0.1.6.tgz#bc2350d3edfdbfadd7ac018c855ae7c13905a6ad" + integrity sha1-vCNQ0+39v63XrAGMhVrnwTkFpq0= + nanoid@^3.1.23: version "3.1.23" resolved "https://registry.nlark.com/nanoid/download/nanoid-3.1.23.tgz?cache=0&sync_timestamp=1620673983269&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnanoid%2Fdownload%2Fnanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" integrity sha1-90QIbOfCvEfuCoRyV01ceOQYOoE= +nanoid@^3.1.25: + version "3.1.25" + resolved "https://registry.nlark.com/nanoid/download/nanoid-3.1.25.tgz?cache=0&sync_timestamp=1628771923493&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnanoid%2Fdownload%2Fnanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" + integrity sha1-CcoydHwOVD8OGBS303k0d/nI4VI= + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -8966,6 +9175,13 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-emoji@^1.11.0: + version "1.11.0" + resolved "https://registry.nlark.com/node-emoji/download/node-emoji-1.11.0.tgz?cache=0&sync_timestamp=1628672555671&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnode-emoji%2Fdownload%2Fnode-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha1-aaAVDmlG4vEV6dfqTfeXHiYoMBw= + dependencies: + lodash "^4.17.21" + node-environment-flags@1.0.5: version "1.0.5" resolved "https://registry.nlark.com/node-environment-flags/download/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" @@ -9040,6 +9256,11 @@ node-releases@^1.1.61, node-releases@^1.1.71: resolved "https://registry.nlark.com/node-releases/download/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" integrity sha1-3U6B3dUnf/hGuAtSu0DEnt96eyA= +node-releases@^1.1.76: + version "1.1.76" + resolved "https://registry.nlark.com/node-releases/download/node-releases-1.1.76.tgz?cache=0&sync_timestamp=1632151316505&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e" + integrity sha1-3yRbBisMr71SgqtnkvfczC2X824= + normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.nlark.com/normalize-package-data/download/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -9144,6 +9365,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.nlark.com/object-hash/download/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha1-WtUYWB7vxEO9djRyuP8unCwNVKU= + object-inspect@^1.10.3, object-inspect@^1.9.0: version "1.10.3" resolved "https://registry.nlark.com/object-inspect/download/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" @@ -9933,6 +10159,14 @@ postcss-initial@^3.0.0: dependencies: postcss "^7.0.2" +postcss-js@^3.0.3: + version "3.0.3" + resolved "https://registry.nlark.com/postcss-js/download/postcss-js-3.0.3.tgz#2f0bd370a2e8599d45439f6970403b5873abda33" + integrity sha1-LwvTcKLoWZ1FQ59pcEA7WHOr2jM= + dependencies: + camelcase-css "^2.0.1" + postcss "^8.1.6" + postcss-lab-function@^2.0.1: version "2.0.1" resolved "https://registry.npm.taobao.org/postcss-lab-function/download/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" @@ -9950,6 +10184,15 @@ postcss-load-config@^2.0.0: cosmiconfig "^5.0.0" import-cwd "^2.0.0" +postcss-load-config@^3.1.0: + version "3.1.0" + resolved "https://registry.nlark.com/postcss-load-config/download/postcss-load-config-3.1.0.tgz#d39c47091c4aec37f50272373a6a648ef5e97829" + integrity sha1-05xHCRxK7Df1AnI3OmpkjvXpeCk= + dependencies: + import-cwd "^3.0.0" + lilconfig "^2.0.3" + yaml "^1.10.2" + postcss-loader@3.0.0: version "3.0.0" resolved "https://registry.nlark.com/postcss-loader/download/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" @@ -10069,6 +10312,13 @@ postcss-modules-values@^3.0.0: icss-utils "^4.0.0" postcss "^7.0.6" +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.nlark.com/postcss-nested/download/postcss-nested-5.0.6.tgz?cache=0&sync_timestamp=1627468122159&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpostcss-nested%2Fdownload%2Fpostcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha1-RmND9/yNPUavPn26P81H0FKpRbw= + dependencies: + postcss-selector-parser "^6.0.6" + postcss-nesting@^7.0.0: version "7.0.1" resolved "https://registry.nlark.com/postcss-nesting/download/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" @@ -10318,7 +10568,7 @@ postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: indexes-of "^1.0.1" uniq "^1.0.1" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.6: version "6.0.6" resolved "https://registry.nlark.com/postcss-selector-parser/download/postcss-selector-parser-6.0.6.tgz?cache=0&sync_timestamp=1620752924836&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpostcss-selector-parser%2Fdownload%2Fpostcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" integrity sha1-LFu6gXSsL2mBq2MaQqsO5UrzMuo= @@ -10344,7 +10594,7 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0: +postcss-value-parser@^3.0.0, postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.nlark.com/postcss-value-parser/download/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha1-n/giVH4okyE88cMO+lGsX9G6goE= @@ -10381,6 +10631,15 @@ postcss@^8.1.0: nanoid "^3.1.23" source-map-js "^0.6.2" +postcss@^8.1.6, postcss@^8.2.1, postcss@^8.3.7: + version "8.3.7" + resolved "https://registry.nlark.com/postcss/download/postcss-8.3.7.tgz?cache=0&sync_timestamp=1632290182939&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpostcss%2Fdownload%2Fpostcss-8.3.7.tgz#ec88563588c8da8e58e7226f7633b51ae221eeda" + integrity sha1-7IhWNYjI2o5Y5yJvdjO1GuIh7to= + dependencies: + nanocolors "^0.1.5" + nanoid "^3.1.25" + source-map-js "^0.6.2" + postcss@^8.3.6: version "8.3.6" resolved "https://registry.nlark.com/postcss/download/postcss-8.3.6.tgz?cache=0&sync_timestamp=1626882933935&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fpostcss%2Fdownload%2Fpostcss-8.3.6.tgz#2730dd76a97969f37f53b9a6096197be311cc4ea" @@ -10428,6 +10687,11 @@ pretty-format@^26.0.0, pretty-format@^26.6.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.nlark.com/pretty-hrtime/download/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -10562,6 +10826,16 @@ pure-color@^1.2.0: resolved "https://registry.npm.taobao.org/pure-color/download/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= +purgecss@^4.0.3: + version "4.0.3" + resolved "https://registry.npm.taobao.org/purgecss/download/purgecss-4.0.3.tgz#8147b429f9c09db719e05d64908ea8b672913742" + integrity sha1-gUe0KfnAnbcZ4F1kkI6otnKRN0I= + dependencies: + commander "^6.0.0" + glob "^7.0.0" + postcss "^8.2.1" + postcss-selector-parser "^6.0.2" + q@^1.1.2: version "1.5.1" resolved "https://registry.nlark.com/q/download/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -10617,6 +10891,11 @@ queue-microtask@^1.2.2: resolved "https://registry.npm.taobao.org/queue-microtask/download/queue-microtask-1.2.3.tgz?cache=0&sync_timestamp=1616391471040&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqueue-microtask%2Fdownload%2Fqueue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha1-SSkii7xyTfrEPg77BYyve2z7YkM= +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.nlark.com/quick-lru/download/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha1-NmST5rPkKjpoheLpnRj4D7eoyTI= + raf-schd@^4.0.2: version "4.0.3" resolved "https://registry.npm.taobao.org/raf-schd/download/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" @@ -11009,6 +11288,14 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +reduce-css-calc@^2.1.8: + version "2.1.8" + resolved "https://registry.npm.taobao.org/reduce-css-calc/download/reduce-css-calc-2.1.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freduce-css-calc%2Fdownload%2Freduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha1-fvh2GijWFJgNwMmC93LJP3qZ3gM= + dependencies: + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" + redux@^4.0.0, redux@^4.0.4: version "4.1.0" resolved "https://registry.nlark.com/redux/download/redux-4.1.0.tgz?cache=0&sync_timestamp=1619286844146&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fredux%2Fdownload%2Fredux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4" @@ -12314,6 +12601,44 @@ table@^6.0.9: string-width "^4.2.0" strip-ansi "^6.0.0" +tailwindcss@^2.2.15: + version "2.2.15" + resolved "https://registry.npmmirror.com/tailwindcss/download/tailwindcss-2.2.15.tgz#8bee3ebe68b988c050508ce20633f35b040dd9fe" + integrity sha1-i+4+vmi5iMBQUIziBjPzWwQN2f4= + dependencies: + arg "^5.0.1" + bytes "^3.0.0" + chalk "^4.1.2" + chokidar "^3.5.2" + color "^4.0.1" + cosmiconfig "^7.0.1" + detective "^5.2.0" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.7" + fs-extra "^10.0.0" + glob-parent "^6.0.1" + html-tags "^3.1.0" + is-color-stop "^1.1.0" + is-glob "^4.0.1" + lodash "^4.17.21" + lodash.topath "^4.5.2" + modern-normalize "^1.1.0" + node-emoji "^1.11.0" + normalize-path "^3.0.0" + object-hash "^2.2.0" + postcss-js "^3.0.3" + postcss-load-config "^3.1.0" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.6" + postcss-value-parser "^4.1.0" + pretty-hrtime "^1.0.3" + purgecss "^4.0.3" + quick-lru "^5.1.1" + reduce-css-calc "^2.1.8" + resolve "^1.20.0" + tmp "^0.2.1" + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.nlark.com/tapable/download/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -12467,6 +12792,13 @@ tiny-invariant@^1.0.6: resolved "https://registry.npm.taobao.org/tiny-invariant/download/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" integrity sha1-Y0xfjv3CdxS384bDXmdgmR0jCHU= +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.nlark.com/tmp/download/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha1-hFf8MDfc9HGcJRNnoa9lAO4czxQ= + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.npm.taobao.org/tmpl/download/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" @@ -14002,7 +14334,7 @@ xmlchars@^2.2.0: resolved "https://registry.npm.taobao.org/xmlchars/download/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha1-Bg/hvLf5x2/ioX24apvDq4lCEMs= -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npm.taobao.org/xtend/download/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha1-u3J3n1+kZRhrH0OPZ0+jR/2121Q= @@ -14027,7 +14359,7 @@ yallist@^4.0.0: resolved "https://registry.nlark.com/yallist/download/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI= -yaml@^1.10.0, yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: version "1.10.2" resolved "https://registry.nlark.com/yaml/download/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha1-IwHF/78StGfejaIzOkWeKeeSDks= From 1f79f3b8ad581fa495ac05a31598e973bd9c4d40 Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Thu, 23 Sep 2021 12:43:28 +0800 Subject: [PATCH 06/10] refactor: remove deps on tableau-ui and fluent-ui for graphic-walker --- packages/graphic-walker/package.json | 3 +- packages/graphic-walker/src/App.tsx | 19 +-- .../graphic-walker/src/Fields/components.tsx | 24 +++ packages/graphic-walker/src/Fields/index.tsx | 21 ++- .../graphic-walker/src/InsightBoard/index.tsx | 5 +- .../graphic-walker/src/components/modal.tsx | 10 +- .../graphic-walker/src/dataSource/index.tsx | 21 ++- .../graphic-walker/src/dataSource/pannel.tsx | 33 ++-- .../graphic-walker/src/dataSource/table.tsx | 8 +- packages/graphic-walker/tailwind.config.js | 3 + yarn.lock | 151 +----------------- 11 files changed, 86 insertions(+), 212 deletions(-) diff --git a/packages/graphic-walker/package.json b/packages/graphic-walker/package.json index d57151ce..8cd994ca 100644 --- a/packages/graphic-walker/package.json +++ b/packages/graphic-walker/package.json @@ -20,8 +20,7 @@ }, "types": "./dist/index.d.ts", "dependencies": { - "@fluentui/react": "^8.29.0", - "@tableau/tableau-ui": "^3.2.0", + "@heroicons/react": "^1.0.4", "autoprefixer": "^10.3.5", "mobx": "^6.3.3", "mobx-react-lite": "^3.2.1", diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index 08a91b1a..f88eeb14 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -8,12 +8,12 @@ import { Container } from './components/container'; import ClickMenu from './components/clickMenu'; import InsightBoard from './InsightBoard'; import { useFieldsState } from './Fields/useFieldsState' -import { Button, DropdownSelect, Checkbox } from '@tableau/tableau-ui'; import Modal from './components/modal'; import DataSourceSegment from './dataSource/index'; import { useGlobalStore } from './store'; import { preAnalysis, destroyWorker } from './services' import { observer } from 'mobx-react-lite'; +import { toJS } from 'mobx'; export interface EditorProps { dataSource?: Record[]; @@ -70,7 +70,7 @@ const App: React.FC = props => { setInsightReady(false) preAnalysis({ dataSource: ds.dataSource, - fields: ds.rawFields + fields: toJS(ds.rawFields) }).then(() => { setInsightReady(true); }) @@ -94,18 +94,15 @@ const App: React.FC = props => {
- { + { setAggregated(e.target.checked) - }} - > - 聚合度量 - + }} /> +
- { setGeomType(e.target.value) }} @@ -115,7 +112,7 @@ const App: React.FC = props => { {g.label} ))} - +
diff --git a/packages/graphic-walker/src/Fields/components.tsx b/packages/graphic-walker/src/Fields/components.tsx index 4d728207..a78105f7 100644 --- a/packages/graphic-walker/src/Fields/components.tsx +++ b/packages/graphic-walker/src/Fields/components.tsx @@ -57,3 +57,27 @@ export const FieldLabel = styled.div<{ highlight?: boolean; type?: 'D' | 'M' }>` props.highlight ? "box-shadow: 0px 0px 5px 1px #21ba45" : null}; position: relative; `; + +export const Pill = styled.div<{colType: 'discrete' | 'continuous'}>` + background-color: ${props => props.colType === 'continuous' ? 'rgb(0, 177, 128)' : 'rgb(73, 150, 178)'}; + color: #fff; + -moz-user-select: none; + -ms-user-select: none; + -webkit-align-items: center; + -webkit-user-select: none; + align-items: center; + border-color: transparent; + border-radius: 10px; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + cursor: default; + display: -webkit-flex; + display: flex; + font-size: 12px; + height: 20px; + min-width: 150px; + overflow-y: hidden; + padding: 0 10px; + user-select: none; +` diff --git a/packages/graphic-walker/src/Fields/index.tsx b/packages/graphic-walker/src/Fields/index.tsx index 38ce0171..ba7a04f1 100644 --- a/packages/graphic-walker/src/Fields/index.tsx +++ b/packages/graphic-walker/src/Fields/index.tsx @@ -9,11 +9,10 @@ import { } from "react-beautiful-dnd"; import styled from "styled-components"; import produce from "immer"; -import { FieldListContainer, FieldsContainer } from "./components"; +import { FieldListContainer, FieldsContainer, Pill } from "./components"; import { move, reorder } from "./utils"; import { Field } from "../interfaces"; import { Container } from '../components/container' -import { Pill, DropdownSelect } from '@tableau/tableau-ui'; const RootContainer = styled.div` font-size: 12px; @@ -154,15 +153,14 @@ const DraggableFields: React.FC = (props) => { {f.name}  {f.type === 'M' && ( - { setState((state) => { @@ -178,7 +176,7 @@ const DraggableFields: React.FC = (props) => { { aggregatorList.map(op => ) } - + )} ); @@ -213,15 +211,14 @@ const DraggableFields: React.FC = (props) => { {f.name}  {f.type === 'M' && ( - { setState((state) => { @@ -240,7 +237,7 @@ const DraggableFields: React.FC = (props) => { {op.label} ))} - + )} ); diff --git a/packages/graphic-walker/src/InsightBoard/index.tsx b/packages/graphic-walker/src/InsightBoard/index.tsx index 8648bb6c..5f7dbf39 100644 --- a/packages/graphic-walker/src/InsightBoard/index.tsx +++ b/packages/graphic-walker/src/InsightBoard/index.tsx @@ -4,7 +4,6 @@ import { Insight, Utils, UnivariateSummary } from 'visual-insights'; import { baseVis, IReasonType } from './std2vegaSpec'; import embed from 'vega-embed'; import { getExplaination, IVisSpace } from '../services'; -import { Spinner } from '@tableau/tableau-ui'; import RadioGroupButtons from './radioGroupButtons'; import { IExplaination, IMeasureWithStat } from '../insights'; import { mergeMeasures } from './utils'; @@ -148,9 +147,7 @@ const InsightBoard: React.FC = props => { {FilterDesc}, {valueDesc}

{loading && ( -
- -
+
)}
diff --git a/packages/graphic-walker/src/components/modal.tsx b/packages/graphic-walker/src/components/modal.tsx index ad592a5e..a3676db1 100644 --- a/packages/graphic-walker/src/components/modal.tsx +++ b/packages/graphic-walker/src/components/modal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from 'styled-components'; -import { IconButton } from '@fluentui/react'; +import { XCircleIcon } from '@heroicons/react/outline'; const Container = styled.div` width: 880px; max-height: 800px; @@ -31,12 +31,10 @@ const Modal: React.FC = props => { const { onClose, title } = props; return ( -
+
{title} -
diff --git a/packages/graphic-walker/src/dataSource/index.tsx b/packages/graphic-walker/src/dataSource/index.tsx index 204b98d9..2a2b30ab 100644 --- a/packages/graphic-walker/src/dataSource/index.tsx +++ b/packages/graphic-walker/src/dataSource/index.tsx @@ -1,5 +1,3 @@ -import { ProgressIndicator } from '@fluentui/react'; -import { Button, DropdownSelect } from '@tableau/tableau-ui'; import { observer } from 'mobx-react-lite'; import React, { useState } from 'react'; import { Container } from '../components/container'; @@ -18,9 +16,10 @@ const DataSourceSegment: React.FC = props => { const { currentDataset, datasets, showDSPanel } = store; return - {!preWorkDone && } - -
} + + { + store.updateTempName(e.target.value) + }} + className="text-xs p-1 border border-gray-300 outline-none focus:outline-none focus:border-blue-500" + />
diff --git a/packages/graphic-walker/src/dataSource/table.tsx b/packages/graphic-walker/src/dataSource/table.tsx index fb87ea17..77e4b3d1 100644 --- a/packages/graphic-walker/src/dataSource/table.tsx +++ b/packages/graphic-walker/src/dataSource/table.tsx @@ -1,8 +1,6 @@ import React from 'react'; import { Record, IField, IMutField } from '../interfaces'; import styled from 'styled-components'; -import { DropdownSelect } from '@tableau/tableau-ui'; -import produce from 'immer'; import { observer } from 'mobx-react-lite'; import { useGlobalStore } from '../store'; @@ -73,13 +71,15 @@ const Table: React.FC = props => { diff --git a/packages/graphic-walker/tailwind.config.js b/packages/graphic-walker/tailwind.config.js index 18cff63d..db7db016 100644 --- a/packages/graphic-walker/tailwind.config.js +++ b/packages/graphic-walker/tailwind.config.js @@ -4,6 +4,9 @@ module.exports = { ], darkMode: false, // or 'media' or 'class' theme: { + minWidth: { + '96': '96px' + }, extend: {}, }, variants: { diff --git a/yarn.lock b/yarn.lock index c70a1390..e357df54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1396,14 +1396,6 @@ "@uifabric/set-version" "^7.0.24" tslib "^1.10.0" -"@fluentui/date-time-utilities@^8.2.2": - version "8.2.2" - resolved "https://registry.nlark.com/@fluentui/date-time-utilities/download/@fluentui/date-time-utilities-8.2.2.tgz#535d5bb6ee7ccfa8cc774c790e31d3d5d4edbad6" - integrity sha1-U11btu58z6jMd0x5DjHT1dTtutY= - dependencies: - "@fluentui/set-version" "^8.1.4" - tslib "^2.1.0" - "@fluentui/dom-utilities@^1.1.2": version "1.1.2" resolved "https://registry.nlark.com/@fluentui/dom-utilities/download/@fluentui/dom-utilities-1.1.2.tgz#1a53e51e1ab1d40696ae7d355a970c68d496845f" @@ -1412,34 +1404,6 @@ "@uifabric/set-version" "^7.0.24" tslib "^1.10.0" -"@fluentui/dom-utilities@^2.1.4": - version "2.1.4" - resolved "https://registry.nlark.com/@fluentui/dom-utilities/download/@fluentui/dom-utilities-2.1.4.tgz?cache=0&sync_timestamp=1627978008693&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40fluentui%2Fdom-utilities%2Fdownload%2F%40fluentui%2Fdom-utilities-2.1.4.tgz#a8eeaf906cc19f547ae40c662d2776cb2540ea11" - integrity sha1-qO6vkGzBn1R65AxmLSd2yyVA6hE= - dependencies: - "@fluentui/set-version" "^8.1.4" - tslib "^2.1.0" - -"@fluentui/font-icons-mdl2@^8.1.9": - version "8.1.9" - resolved "https://registry.nlark.com/@fluentui/font-icons-mdl2/download/@fluentui/font-icons-mdl2-8.1.9.tgz#fa22050d3d8a22a4247ebff5513f617dd757d488" - integrity sha1-+iIFDT2KIqQkfr/1UT9hfddX1Ig= - dependencies: - "@fluentui/set-version" "^8.1.4" - "@fluentui/style-utilities" "^8.3.0" - tslib "^2.1.0" - -"@fluentui/foundation-legacy@^8.1.9": - version "8.1.9" - resolved "https://registry.nlark.com/@fluentui/foundation-legacy/download/@fluentui/foundation-legacy-8.1.9.tgz#2e31f4db71f3f0f799b78a2740978cee2fe27559" - integrity sha1-LjH023Hz8PeZt4onQJeM7i/idVk= - dependencies: - "@fluentui/merge-styles" "^8.1.4" - "@fluentui/set-version" "^8.1.4" - "@fluentui/style-utilities" "^8.3.0" - "@fluentui/utilities" "^8.3.0" - tslib "^2.1.0" - "@fluentui/keyboard-key@^0.2.12": version "0.2.17" resolved "https://registry.nlark.com/@fluentui/keyboard-key/download/@fluentui/keyboard-key-0.2.17.tgz#61c037c3a596986926ad8812b58e9e5cebf9f972" @@ -1447,21 +1411,6 @@ dependencies: tslib "^1.10.0" -"@fluentui/keyboard-key@^0.3.4": - version "0.3.4" - resolved "https://registry.nlark.com/@fluentui/keyboard-key/download/@fluentui/keyboard-key-0.3.4.tgz#27c95ea9d43d91cc9c64c318feb10986250584cd" - integrity sha1-J8leqdQ9kcycZMMY/rEJhiUFhM0= - dependencies: - tslib "^2.1.0" - -"@fluentui/merge-styles@^8.1.4": - version "8.1.4" - resolved "https://registry.nlark.com/@fluentui/merge-styles/download/@fluentui/merge-styles-8.1.4.tgz#d08035eee47945f48d991ed2555e432d49e892b5" - integrity sha1-0IA17uR5RfSNmR7SVV5DLUnokrU= - dependencies: - "@fluentui/set-version" "^8.1.4" - tslib "^2.1.0" - "@fluentui/react-focus@^7.17.6": version "7.17.6" resolved "https://registry.nlark.com/@fluentui/react-focus/download/@fluentui/react-focus-7.17.6.tgz#aa23413c56d27615fdc8363bf8b8c70d5817c929" @@ -1474,28 +1423,6 @@ "@uifabric/utilities" "^7.33.5" tslib "^1.10.0" -"@fluentui/react-focus@^8.1.11": - version "8.1.11" - resolved "https://registry.nlark.com/@fluentui/react-focus/download/@fluentui/react-focus-8.1.11.tgz#ced96a040858e36e4acbd493054c3a6f997dccc0" - integrity sha1-ztlqBAhY425Ky9STBUw6b5l9zMA= - dependencies: - "@fluentui/keyboard-key" "^0.3.4" - "@fluentui/merge-styles" "^8.1.4" - "@fluentui/set-version" "^8.1.4" - "@fluentui/style-utilities" "^8.3.0" - "@fluentui/utilities" "^8.3.0" - tslib "^2.1.0" - -"@fluentui/react-hooks@^8.2.7": - version "8.2.7" - resolved "https://registry.nlark.com/@fluentui/react-hooks/download/@fluentui/react-hooks-8.2.7.tgz#7521dd16c41b8acf12a7e67142d5bd13da2dbeed" - integrity sha1-dSHdFsQbis8Sp+ZxQtW9E9otvu0= - dependencies: - "@fluentui/react-window-provider" "^2.1.4" - "@fluentui/set-version" "^8.1.4" - "@fluentui/utilities" "^8.3.0" - tslib "^2.1.0" - "@fluentui/react-window-provider@^1.0.2": version "1.0.2" resolved "https://registry.nlark.com/@fluentui/react-window-provider/download/@fluentui/react-window-provider-1.0.2.tgz#634f1f2e77fca9b3e8dbff9599d0c86d6c77dd73" @@ -1504,52 +1431,6 @@ "@uifabric/set-version" "^7.0.24" tslib "^1.10.0" -"@fluentui/react-window-provider@^2.1.4": - version "2.1.4" - resolved "https://registry.nlark.com/@fluentui/react-window-provider/download/@fluentui/react-window-provider-2.1.4.tgz?cache=0&sync_timestamp=1627978008240&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40fluentui%2Freact-window-provider%2Fdownload%2F%40fluentui%2Freact-window-provider-2.1.4.tgz#2e8368fd85f9e10062c726b060b146ffc1f916b3" - integrity sha1-LoNo/YX54QBixyawYLFG/8H5FrM= - dependencies: - "@fluentui/set-version" "^8.1.4" - tslib "^2.1.0" - -"@fluentui/react@^8.29.0": - version "8.29.0" - resolved "https://registry.nlark.com/@fluentui/react/download/@fluentui/react-8.29.0.tgz#e1e233315a541ce36edf970d5938b8ed448011bb" - integrity sha1-4eIzMVpUHONu35cNWTi47USAEbs= - dependencies: - "@fluentui/date-time-utilities" "^8.2.2" - "@fluentui/font-icons-mdl2" "^8.1.9" - "@fluentui/foundation-legacy" "^8.1.9" - "@fluentui/merge-styles" "^8.1.4" - "@fluentui/react-focus" "^8.1.11" - "@fluentui/react-hooks" "^8.2.7" - "@fluentui/react-window-provider" "^2.1.4" - "@fluentui/set-version" "^8.1.4" - "@fluentui/style-utilities" "^8.3.0" - "@fluentui/theme" "^2.2.2" - "@fluentui/utilities" "^8.3.0" - "@microsoft/load-themed-styles" "^1.10.26" - tslib "^2.1.0" - -"@fluentui/set-version@^8.1.4": - version "8.1.4" - resolved "https://registry.nlark.com/@fluentui/set-version/download/@fluentui/set-version-8.1.4.tgz#89fa88223f421981427dfd5372d46210045354e8" - integrity sha1-ifqIIj9CGYFCff1TctRiEARTVOg= - dependencies: - tslib "^2.1.0" - -"@fluentui/style-utilities@^8.3.0": - version "8.3.0" - resolved "https://registry.nlark.com/@fluentui/style-utilities/download/@fluentui/style-utilities-8.3.0.tgz#dcc76b310da0e4366a249c7e9c5ecedcfd212f33" - integrity sha1-3MdrMQ2g5DZqJJx+nF7O3P0hLzM= - dependencies: - "@fluentui/merge-styles" "^8.1.4" - "@fluentui/set-version" "^8.1.4" - "@fluentui/theme" "^2.2.2" - "@fluentui/utilities" "^8.3.0" - "@microsoft/load-themed-styles" "^1.10.26" - tslib "^2.1.0" - "@fluentui/theme@^1.7.4": version "1.7.4" resolved "https://registry.nlark.com/@fluentui/theme/download/@fluentui/theme-1.7.4.tgz#8582bab5a7445585c631d05d44b5ebb56f18b6ba" @@ -1560,26 +1441,6 @@ "@uifabric/utilities" "^7.33.5" tslib "^1.10.0" -"@fluentui/theme@^2.2.2": - version "2.2.2" - resolved "https://registry.nlark.com/@fluentui/theme/download/@fluentui/theme-2.2.2.tgz#5a381d9904424f161a32383835d4beaf6e237b98" - integrity sha1-WjgdmQRCTxYaMjg4NdS+r24je5g= - dependencies: - "@fluentui/merge-styles" "^8.1.4" - "@fluentui/set-version" "^8.1.4" - "@fluentui/utilities" "^8.3.0" - tslib "^2.1.0" - -"@fluentui/utilities@^8.3.0": - version "8.3.0" - resolved "https://registry.nlark.com/@fluentui/utilities/download/@fluentui/utilities-8.3.0.tgz#cee8088424b5632fd433b7caae863266f1b77ec9" - integrity sha1-zugIhCS1Yy/UM7fKroYyZvG3fsk= - dependencies: - "@fluentui/dom-utilities" "^2.1.4" - "@fluentui/merge-styles" "^8.1.4" - "@fluentui/set-version" "^8.1.4" - tslib "^2.1.0" - "@formatjs/intl-unified-numberformat@^3.2.0": version "3.3.7" resolved "https://registry.npm.taobao.org/@formatjs/intl-unified-numberformat/download/@formatjs/intl-unified-numberformat-3.3.7.tgz#9995a24568908188e716d81a1de5b702b2ee00e2" @@ -1624,6 +1485,11 @@ dependencies: "@hapi/hoek" "^8.3.0" +"@heroicons/react@^1.0.4": + version "1.0.4" + resolved "https://registry.nlark.com/@heroicons/react/download/@heroicons/react-1.0.4.tgz#11847eb2ea5510419d7ada9ff150a33af0ad0863" + integrity sha1-EYR+supVEEGdetqf8VCjOvCtCGM= + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.nlark.com/@istanbuljs/load-nyc-config/download/@istanbuljs/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2039,7 +1905,7 @@ "@svgr/plugin-svgo" "^5.4.0" loader-utils "^2.0.0" -"@tableau/tableau-ui@^3.0.0", "@tableau/tableau-ui@^3.2.0": +"@tableau/tableau-ui@^3.0.0": version "3.2.0" resolved "https://registry.nlark.com/@tableau/tableau-ui/download/@tableau/tableau-ui-3.2.0.tgz#25277a07111e281042d70cd610e52ba1104aaf25" integrity sha1-JSd6BxEeKBBC1wzWEOUroRBKryU= @@ -12927,11 +12793,6 @@ tslib@^2.0.3, tslib@^2.2.0: resolved "https://registry.nlark.com/tslib/download/tslib-2.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftslib%2Fdownload%2Ftslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha1-gDuM2rPhK6WBpMpByIObuw2ssJ4= -tslib@^2.1.0: - version "2.3.1" - resolved "https://registry.nlark.com/tslib/download/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha1-6KM1rdXOrlGqJh0ypJAVjvBC7wE= - tslib@~2.0.3: version "2.0.3" resolved "https://registry.nlark.com/tslib/download/tslib-2.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Ftslib%2Fdownload%2Ftslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" From f1203928324d04d253e6550cf867e81ed2dd0c2c Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Thu, 23 Sep 2021 13:00:05 +0800 Subject: [PATCH 07/10] fix: modal height --- packages/graphic-walker/src/components/modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphic-walker/src/components/modal.tsx b/packages/graphic-walker/src/components/modal.tsx index a3676db1..87e37a16 100644 --- a/packages/graphic-walker/src/components/modal.tsx +++ b/packages/graphic-walker/src/components/modal.tsx @@ -31,7 +31,7 @@ const Modal: React.FC = props => { const { onClose, title } = props; return ( -
+
{title} Date: Fri, 24 Sep 2021 18:35:27 +0800 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=E6=96=B0UI=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/graphic-walker/src/App.tsx | 152 ++++-------- .../src/Fields/AestheticFields.tsx | 64 +++++ .../src/Fields/DatasetFields.tsx | 58 +++++ .../graphic-walker/src/Fields/components.tsx | 44 +++- .../src/Fields/fieldsContext.tsx | 79 ++++++ .../graphic-walker/src/Fields/posFields.tsx | 66 +++++ .../graphic-walker/src/InsightBoard/index.tsx | 226 +++--------------- .../src/InsightBoard/mainBoard.tsx | 198 +++++++++++++++ .../src/InsightBoard/radioGroupButtons.tsx | 3 +- .../src/components/container.tsx | 9 +- .../graphic-walker/src/components/modal.tsx | 7 +- packages/graphic-walker/src/config.ts | 10 +- .../graphic-walker/src/dataSource/index.tsx | 14 +- .../graphic-walker/src/dataSource/pannel.tsx | 15 +- .../graphic-walker/src/dataSource/table.tsx | 33 ++- packages/graphic-walker/src/index.tsx | 5 +- packages/graphic-walker/src/interfaces.ts | 21 ++ .../graphic-walker/src/renderer/index.tsx | 39 +++ .../graphic-walker/src/store/commonStore.ts | 163 +++++++++++++ packages/graphic-walker/src/store/index.tsx | 144 ++--------- .../src/store/visualSpecStore.ts | 91 +++++++ .../src/visualSettings/index.tsx | 42 ++++ packages/graphic-walker/tailwind.config.js | 4 +- 23 files changed, 1023 insertions(+), 464 deletions(-) create mode 100644 packages/graphic-walker/src/Fields/AestheticFields.tsx create mode 100644 packages/graphic-walker/src/Fields/DatasetFields.tsx create mode 100644 packages/graphic-walker/src/Fields/fieldsContext.tsx create mode 100644 packages/graphic-walker/src/Fields/posFields.tsx create mode 100644 packages/graphic-walker/src/InsightBoard/mainBoard.tsx create mode 100644 packages/graphic-walker/src/renderer/index.tsx create mode 100644 packages/graphic-walker/src/store/commonStore.ts create mode 100644 packages/graphic-walker/src/store/visualSpecStore.ts create mode 100644 packages/graphic-walker/src/visualSettings/index.tsx diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index f88eeb14..b519e1cf 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -2,11 +2,14 @@ import React, { useState, useEffect, useCallback } from 'react'; import DraggableFields from './Fields'; import { Record, Filters, Field, IMutField } from './interfaces'; import ReactVega from './vis/react-vega'; -import { GEMO_TYPES } from './config'; -import { LiteForm } from './components/liteForm'; -import { Container } from './components/container'; +import VisualSettings from './visualSettings'; +import { Container, NestContainer } from './components/container'; import ClickMenu from './components/clickMenu'; -import InsightBoard from './InsightBoard'; +import InsightBoard from './InsightBoard/index'; +import PosFields from './Fields/posFields'; +import AestheticFields from './Fields/AestheticFields'; +import DatasetFields from './Fields/DatasetFields'; +import ReactiveRenderer from './renderer/index'; import { useFieldsState } from './Fields/useFieldsState' import Modal from './components/modal'; import DataSourceSegment from './dataSource/index'; @@ -22,23 +25,23 @@ export interface EditorProps { const App: React.FC = props => { const { dataSource = [], rawFields = [] } = props; - const store = useGlobalStore(); - const [fields, setFields] = useState([]); - const [geomType, setGeomType] = useState(GEMO_TYPES[0].value); - const [aggregated, setAggregated] = useState(true); - const [position, setPosition] = useState<[number, number]>([0, 0]); - const [showMenu, setShowMenu] = useState(false); - const [showInsight, setShowInsight] = useState(false); - const [filters, setFilters] = useState({}); + const { commonStore, vizStore } = useGlobalStore(); + // const [fields, setFields] = useState([]); + // const [geomType, setGeomType] = useState(GEMO_TYPES[0].value); + // const [aggregated, setAggregated] = useState(true); + // const [position, setPosition] = useState<[number, number]>([0, 0]); + // const [showMenu, setShowMenu] = useState(false); + // const [showInsight, setShowInsight] = useState(false); + // const [filters, setFilters] = useState({}); const [insightReady, setInsightReady] = useState(true); - const { fstate, setFstate, viewDimensions, viewMeasures } = useFieldsState(); - const { currentDataset, datasets } = store; + const { currentDataset, datasets, filters, vizEmbededMenu, showInsightBoard } = commonStore; + const { viewDimensions, viewMeasures, draggableFieldState } = vizStore // use as an embeding module, use outside datasource from props. useEffect(() => { if (dataSource.length > 0) { - store.addAndUseDS({ + commonStore.addAndUseDS({ name: 'context dataset', dataSource: dataSource, rawFields @@ -46,23 +49,6 @@ const App: React.FC = props => { } }, [dataSource, rawFields]) - // change selected dataset, update fields, ... - useEffect(() => { - const fs: Field[] = []; - const ds = currentDataset; - if (ds) { - ds.rawFields.forEach((f) => { - fs.push({ - id: f.key, - name: f.key, - type: f.analyticType === 'dimension' ? 'D' : 'M', - aggName: f.analyticType === 'measure' ? 'sum' : undefined, - }) - }) - setFields(fs) - } - }, [currentDataset]); - // do preparation analysis work when using a new dataset useEffect(() => { const ds = currentDataset; @@ -83,86 +69,46 @@ const App: React.FC = props => { return (
- + {/* { setFstate(state) }} fields={fields} /> - + */} + - -
- { - setAggregated(e.target.checked) - }} /> - +
+
+ +
+
+ +
+
+
+
-
- - -
- + + {datasets.length > 0 && } + + {vizEmbededMenu.show && ( + +
{ + commonStore.closeEmbededMenu(); + commonStore.setShowInsightBoard(true) + }} + > + 深度解读 +
+
+ )} +
+
+
- {datasets.length > 0 && ( - - {showInsight && ( - { - setShowInsight(false) - }} - > - - - )} - {showMenu && ( - -
{ - setShowMenu(false) - setShowInsight(true) - }} - > - 深度解读 -
-
- )} - - { - setFilters(values) - setPosition([e.pageX, e.pageY]) - setShowMenu(true) - }} - /> -
- )}
) } diff --git a/packages/graphic-walker/src/Fields/AestheticFields.tsx b/packages/graphic-walker/src/Fields/AestheticFields.tsx new file mode 100644 index 00000000..240f144c --- /dev/null +++ b/packages/graphic-walker/src/Fields/AestheticFields.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + ResponderProvided, + DraggableLocation, +} from "react-beautiful-dnd"; +import { AGGREGATOR_LIST, DRAGGABLE_STATE_KEYS } from './fieldsContext'; +import { AestheticFieldContainer, FieldsContainer, Pill } from './components' +import { useGlobalStore } from '../store/index' + +const aestheticFields = DRAGGABLE_STATE_KEYS.filter(f => ['color', 'opacity', 'size'].includes(f.id)); + +const AestheticFields: React.FC = props => { + const { vizStore } = useGlobalStore(); + const { draggableFieldState } = vizStore; + return
+ { + aestheticFields.map(dkey => + + {(provided, snapshot) => ( + + {draggableFieldState[dkey.id].map((f, index) => ( + + {(provided, snapshot) => { + return ( + + {f.name}  + {f.type === 'M' && ( + + )} + + ); + }} + + ))} + + )} + + ) + } +
+} + +export default AestheticFields; \ No newline at end of file diff --git a/packages/graphic-walker/src/Fields/DatasetFields.tsx b/packages/graphic-walker/src/Fields/DatasetFields.tsx new file mode 100644 index 00000000..920fe994 --- /dev/null +++ b/packages/graphic-walker/src/Fields/DatasetFields.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { FieldListContainer, FieldsContainer, Pill } from "./components"; +import { NestContainer } from '../components/container' +import { observer } from 'mobx-react-lite'; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + ResponderProvided, + DraggableLocation, + } from "react-beautiful-dnd"; + +import { AGGREGATOR_LIST, DRAGGABLE_STATE_KEYS } from './fieldsContext'; +import { useGlobalStore } from '../store'; + +const rowsAndCols = DRAGGABLE_STATE_KEYS.filter(f => f.id === 'fields'); +const DatasetFields: React.FC = props => { + const { vizStore } = useGlobalStore(); + const { draggableFieldState } = vizStore; + + return +

字段列表

+ { + rowsAndCols.map(dkey =>
+ + {(provided, snapshot) => ( +
+ {draggableFieldState[dkey.id].map((f, index) => ( + + {(provided, snapshot) => { + return ( +
+ {f.name}  +
+ ); + }} +
+ ))} +
+ )} +
+
) + } +
+} + +export default observer(DatasetFields); \ No newline at end of file diff --git a/packages/graphic-walker/src/Fields/components.tsx b/packages/graphic-walker/src/Fields/components.tsx index a78105f7..1d25c332 100644 --- a/packages/graphic-walker/src/Fields/components.tsx +++ b/packages/graphic-walker/src/Fields/components.tsx @@ -1,5 +1,25 @@ import React from "react"; import styled from "styled-components"; +import { COLORS } from "../config"; + +export const AestheticSegment = styled.div` + border: 1px solid #dfe3e8; + font-size: 12px; + margin: 0.2em; + + .aes-header{ + border-bottom: 1px solid #dfe3e8; + background-color: #f5f5f5; + padding: 0.6em; + h4 { + font-weight: 400; + } + } + .aes-container{ + + } + +` export const FieldListContainer: React.FC<{ name: string }> = (props) => { return ( @@ -12,21 +32,32 @@ export const FieldListContainer: React.FC<{ name: string }> = (props) => { ); }; +export const AestheticFieldContainer: React.FC<{ name: string }> = props => { + return ( + +
+

{props.name}

+
+
{props.children}
+
+ ); +} + export const FieldsContainer = styled.div` display: flex; padding: 0.2em; min-height: 2.4em; - display: flex; - flex-wrap: wrap; - >div{ - margin: 1px; - } + flex-wrap: wrap; + >div{ + margin: 1px; + } `; export const FieldListSegment = styled.div` display: flex; border: 1px solid #dfe3e8; margin: 0.2em; + font-size: 12px; div.fl-header { /* flex-basis: 100px; */ width: 100px; @@ -59,7 +90,7 @@ export const FieldLabel = styled.div<{ highlight?: boolean; type?: 'D' | 'M' }>` `; export const Pill = styled.div<{colType: 'discrete' | 'continuous'}>` - background-color: ${props => props.colType === 'continuous' ? 'rgb(0, 177, 128)' : 'rgb(73, 150, 178)'}; + background-color: ${props => props.colType === 'continuous' ? COLORS.measure : COLORS.dimension}; color: #fff; -moz-user-select: none; -ms-user-select: none; @@ -81,3 +112,4 @@ export const Pill = styled.div<{colType: 'discrete' | 'continuous'}>` padding: 0 10px; user-select: none; ` + diff --git a/packages/graphic-walker/src/Fields/fieldsContext.tsx b/packages/graphic-walker/src/Fields/fieldsContext.tsx new file mode 100644 index 00000000..f5b81a9b --- /dev/null +++ b/packages/graphic-walker/src/Fields/fieldsContext.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { Field } from '../interfaces'; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + ResponderProvided, + DraggableLocation, +} from "react-beautiful-dnd"; +import { useGlobalStore } from '../store'; +import { useCallback } from 'react'; + +export interface DraggableFieldState { + fields: Field[]; + rows: Field[]; + columns: Field[]; + color: Field[]; + opacity: Field[]; + size: Field[]; +} + +interface DraggableFieldsProps { + fields: Field[]; + onStateChange?: (state: DraggableFieldState) => void; +} + +export const FieldsContextWrapper: React.FC = props => { + const { vizStore } = useGlobalStore(); + const onDragEnd = useCallback((result: DropResult, provided: ResponderProvided) => { + if (!result.destination) { + return; + } + const destination = result.destination as DraggableLocation; + if (destination.droppableId === result.source.droppableId) { + if (destination.index === result.source.index) return; + vizStore.reorderField(destination.droppableId as keyof DraggableFieldState, result.source.index, destination.index); + } else { + let sourceKey = result.source + .droppableId as keyof DraggableFieldState; + let targetKey = destination + .droppableId as keyof DraggableFieldState; + vizStore.moveField(sourceKey, result.source.index, targetKey, destination.index) + } + }, []) + return + { props.children } + +} + +export default FieldsContextWrapper; + +export const DRAGGABLE_STATE_KEYS: Array<{ + id: keyof DraggableFieldState; + name: string; + mode: number + }> = [ + { id: 'fields', name: '字段', mode: 0 }, + { id: 'columns', name: '列', mode: 0 }, + { id: 'rows', name: '行', mode: 0 }, + { id: 'color', name: '颜色', mode: 1 }, + { id: 'opacity', name: '透明度', mode: 1 }, + { id: 'size', name: '大小', mode: 1 }, +]; + +export const AGGREGATOR_LIST = [ + { + value: "sum", + label: "求和", + }, + { + value: "mean", + label: "平均值", + }, + { + value: "count", + label: "计数", + }, +]; diff --git a/packages/graphic-walker/src/Fields/posFields.tsx b/packages/graphic-walker/src/Fields/posFields.tsx new file mode 100644 index 00000000..259ef716 --- /dev/null +++ b/packages/graphic-walker/src/Fields/posFields.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { FieldListContainer, FieldsContainer, Pill } from "./components"; +import { observer } from 'mobx-react-lite'; +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + ResponderProvided, + DraggableLocation, + } from "react-beautiful-dnd"; + +import { AGGREGATOR_LIST, DRAGGABLE_STATE_KEYS } from './fieldsContext'; +import { useGlobalStore } from '../store'; +import { useCallback } from 'react'; +const rowsAndCols = DRAGGABLE_STATE_KEYS.filter(f => f.id === 'columns' || f.id === 'rows'); +const PosFields: React.FC = props => { + const { vizStore } = useGlobalStore(); + const { draggableFieldState } = vizStore; + + return
+ { + rowsAndCols.map(dkey => + + {(provided, snapshot) => ( + + {draggableFieldState[dkey.id].map((f, index) => ( + + {(provided, snapshot) => { + return ( + + {f.name}  + {f.type === 'M' && ( + + )} + + ); + }} + + ))} + + )} + + ) + } +
+} + +export default observer(PosFields); \ No newline at end of file diff --git a/packages/graphic-walker/src/InsightBoard/index.tsx b/packages/graphic-walker/src/InsightBoard/index.tsx index 5f7dbf39..d173bca2 100644 --- a/packages/graphic-walker/src/InsightBoard/index.tsx +++ b/packages/graphic-walker/src/InsightBoard/index.tsx @@ -1,200 +1,30 @@ -import React, { useEffect, useState, useMemo, useRef } from 'react'; -import { Record, Field, Filters, IMeasure } from '../interfaces'; -import { Insight, Utils, UnivariateSummary } from 'visual-insights'; -import { baseVis, IReasonType } from './std2vegaSpec'; -import embed from 'vega-embed'; -import { getExplaination, IVisSpace } from '../services'; -import RadioGroupButtons from './radioGroupButtons'; -import { IExplaination, IMeasureWithStat } from '../insights'; -import { mergeMeasures } from './utils'; -import ReactJson from 'react-json-view'; - -const collection = Insight.IntentionWorkerCollection.init(); - -const ReasonTypeNames: { [key: string]: string} = { - 'selection_dim_distribution': '维度线索', - 'selection_mea_distribution': '度量线索', - 'children_major_factor': '子节点主因', - 'children_outlier': '子节点异常' -} -collection.enable(Insight.DefaultIWorker.cluster, false); -interface SubSpace { - dimensions: string[]; - measures: IMeasure[]; -} - -interface InsightBoardProps { - dataSource: Record[]; - fields: Field[]; - filters?: Filters; - viewDs: Field[]; - viewMs: Field[]; -} -const InsightBoard: React.FC = props => { - const { dataSource, fields, viewDs, viewMs, filters } = props; - const [recSpaces, setRecSpaces] = useState([]); - const [visSpaces, setVisSpaces] = useState([]); - const [visIndex, setVisIndex] = useState(0); - const [loading, setLoading] = useState(false); - const [valueExp, setValueExp] = useState([]); - const container = useRef(null); - - const dimsWithTypes = useMemo(() => { - const dimensions = fields - .filter((f) => f.type === 'D') - .map((f) => f.id) - .filter((f) => !Utils.isFieldUnique(dataSource, f)); - return UnivariateSummary.getAllFieldTypes(dataSource, dimensions); - }, [fields, dataSource]) - - const measWithTypes = useMemo(() => { - const measures = fields.filter((f) => f.type === 'M').map((f) => f.id); - return measures.map((m) => ({ - name: m, - type: 'quantitative', - })); - }, [fields]); - - useEffect(() => { - if (dimsWithTypes.length > 0 && measWithTypes.length > 0 && dataSource.length > 0) { - const measures = fields.filter((f) => f.type === 'M').map((f) => f.id); - const dimensions = dimsWithTypes.map(d => d.name); - const currentSpace: SubSpace = { - dimensions: viewDs.map((f) => f.id), - measures: viewMs.map((f) => ({ - key: f.id, - op: f.aggName as any - })), - }; - setLoading(true); - - getExplaination({ - dimensions, - measures, - dataSource, - currentSpace, - filters - }).then(({ visSpaces, explainations, valueExp }) => { - console.log({ visSpaces, explainations, valueExp }) - setRecSpaces(explainations); - setVisSpaces(visSpaces); - setValueExp(valueExp); - setLoading(false); - }) - } - }, [fields, viewDs, viewMs, measWithTypes, filters, dimsWithTypes, measWithTypes, dataSource]) - - const fieldsWithType = useMemo(() => { - return [...dimsWithTypes, ...measWithTypes]; - }, [dimsWithTypes, measWithTypes]) - - useEffect(() => { - const RecSpace = recSpaces[visIndex]; - const visSpec = visSpaces[visIndex]; - if (container.current && RecSpace && visSpec) { - const usePredicates: boolean = - RecSpace.type === 'selection_dim_distribution' || - RecSpace.type === 'selection_mea_distribution'; - const mergedMeasures = mergeMeasures(RecSpace.measures, RecSpace.extendMs); - const _vegaSpec = baseVis( - visSpec.schema, - visSpec.schema.geomType && visSpec.schema.geomType[0] === 'point' - ? dataSource - : visSpec.dataView, - // result.aggData, - [...RecSpace.dimensions, ...RecSpace.extendDs], - [...RecSpace.measures, ...RecSpace.extendMs].map(m => m.key), - usePredicates ? RecSpace.predicates : null, - mergedMeasures.map((m) => ({ - op: m.op, - field: m.key, - as: m.key, - })), - fieldsWithType as any, - RecSpace.type as IReasonType, - true, - true - ); - if (container.current) { - console.log(_vegaSpec) - embed(container.current, _vegaSpec); - } - } - }, [visIndex, recSpaces, visSpaces, fieldsWithType, dataSource]) - - const FilterDesc = useMemo(() => { - if (filters) { - const dimValues = Object.keys(filters) - .filter(k => filters[k].length > 0) - .map((k) => { - return `${k}=${filters[k]}`; - }); - return `选中对象${dimValues.join(', ')}的数据`; - } - return '' - }, [filters]) - - const valueDesc = useMemo(() => { - const meaStatus = valueExp.map( - (mea) => `${mea.key}(${mea.op})的取值${mea.score === 1 ? '大于' : '小于'}预期` - ); - return meaStatus.join(', '); - }, [valueExp]) - - return ( -
-

- {FilterDesc}, {valueDesc} -

- {loading && ( -
- )} -
-
- ({ - value: s.type || '' + i, - label: `${s.type ? ReasonTypeNames[s.type] : '未识别'}: ${ - s.score.toFixed(2) - }`, - }))} - onChange={(v, i) => { - setVisIndex(i); - }} - /> -
-
-
- {recSpaces[visIndex] && ( -
- 维度是{recSpaces[visIndex].dimensions.join(', ')}。
- 度量是{recSpaces[visIndex].measures.map((m) => m.key).join(', ')}。
- 此时具有 - {recSpaces[visIndex].type - ? ReasonTypeNames[recSpaces[visIndex].type!] - : '未识别'}{' '} - ,评分为{recSpaces[visIndex].score}。 -
- {recSpaces[visIndex].description && - recSpaces[visIndex].description.intMeasures && - FilterDesc + - recSpaces[visIndex].description.intMeasures - .map( - (mea: any) => - `${mea.key}(${mea.op})}的取值${ - mea.score === 1 ? '大于' : '小于' - }预期` - ) - .join(', ')} -
- -
- )} -
-
-
- ); +import { toJS } from 'mobx'; +import { observer } from 'mobx-react-lite'; +import React, { useCallback } from 'react'; +import Modal from '../components/modal'; +import { useGlobalStore } from '../store'; +import InsightMainBoard from './mainBoard'; + +const InsightBoard: React.FC = props => { + const { commonStore, vizStore } = useGlobalStore(); + const { showInsightBoard, currentDataset, filters } = commonStore; + const { viewDimensions, viewMeasures, draggableFieldState } = vizStore; + const onCloseModal = useCallback(() => { + commonStore.setShowInsightBoard(false); + }, []) + return
+ { + showInsightBoard && + + + } +
} -export default InsightBoard; +export default observer(InsightBoard); \ No newline at end of file diff --git a/packages/graphic-walker/src/InsightBoard/mainBoard.tsx b/packages/graphic-walker/src/InsightBoard/mainBoard.tsx new file mode 100644 index 00000000..90626222 --- /dev/null +++ b/packages/graphic-walker/src/InsightBoard/mainBoard.tsx @@ -0,0 +1,198 @@ +import React, { useEffect, useState, useMemo, useRef } from 'react'; +import { Record, Field, Filters, IMeasure } from '../interfaces'; +import { Insight, Utils, UnivariateSummary } from 'visual-insights'; +import { baseVis, IReasonType } from './std2vegaSpec'; +import embed from 'vega-embed'; +import { getExplaination, IVisSpace } from '../services'; +import RadioGroupButtons from './radioGroupButtons'; +import { IExplaination, IMeasureWithStat } from '../insights'; +import { mergeMeasures } from './utils'; +import ReactJson from 'react-json-view'; + +const collection = Insight.IntentionWorkerCollection.init(); + +const ReasonTypeNames: { [key: string]: string} = { + 'selection_dim_distribution': '维度线索', + 'selection_mea_distribution': '度量线索', + 'children_major_factor': '子节点主因', + 'children_outlier': '子节点异常' +} +collection.enable(Insight.DefaultIWorker.cluster, false); +interface SubSpace { + dimensions: string[]; + measures: IMeasure[]; +} + +interface InsightMainBoardProps { + dataSource: Record[]; + fields: Field[]; + filters?: Filters; + viewDs: Field[]; + viewMs: Field[]; +} +const InsightMainBoard: React.FC = props => { + const { dataSource, fields, viewDs, viewMs, filters } = props; + const [recSpaces, setRecSpaces] = useState([]); + const [visSpaces, setVisSpaces] = useState([]); + const [visIndex, setVisIndex] = useState(0); + const [loading, setLoading] = useState(false); + const [valueExp, setValueExp] = useState([]); + const container = useRef(null); + + const dimsWithTypes = useMemo(() => { + const dimensions = fields + .filter((f) => f.type === 'D') + .map((f) => f.id) + .filter((f) => !Utils.isFieldUnique(dataSource, f)); + return UnivariateSummary.getAllFieldTypes(dataSource, dimensions); + }, [fields, dataSource]) + + const measWithTypes = useMemo(() => { + const measures = fields.filter((f) => f.type === 'M').map((f) => f.id); + return measures.map((m) => ({ + name: m, + type: 'quantitative', + })); + }, [fields]); + + useEffect(() => { + if (dimsWithTypes.length > 0 && measWithTypes.length > 0 && dataSource.length > 0) { + const measures = fields.filter((f) => f.type === 'M').map((f) => f.id); + const dimensions = dimsWithTypes.map(d => d.name); + const currentSpace: SubSpace = { + dimensions: viewDs.map((f) => f.id), + measures: viewMs.map((f) => ({ + key: f.id, + op: f.aggName as any + })), + }; + setLoading(true); + + getExplaination({ + dimensions, + measures, + dataSource, + currentSpace, + filters + }).then(({ visSpaces, explainations, valueExp }) => { + setRecSpaces(explainations); + setVisSpaces(visSpaces); + setValueExp(valueExp); + setLoading(false); + }) + } + }, [fields, viewDs, viewMs, measWithTypes, filters, dimsWithTypes, measWithTypes, dataSource]) + + const fieldsWithType = useMemo(() => { + return [...dimsWithTypes, ...measWithTypes]; + }, [dimsWithTypes, measWithTypes]) + + useEffect(() => { + const RecSpace = recSpaces[visIndex]; + const visSpec = visSpaces[visIndex]; + if (container.current && RecSpace && visSpec) { + const usePredicates: boolean = + RecSpace.type === 'selection_dim_distribution' || + RecSpace.type === 'selection_mea_distribution'; + const mergedMeasures = mergeMeasures(RecSpace.measures, RecSpace.extendMs); + const _vegaSpec = baseVis( + visSpec.schema, + visSpec.schema.geomType && visSpec.schema.geomType[0] === 'point' + ? dataSource + : visSpec.dataView, + // result.aggData, + [...RecSpace.dimensions, ...RecSpace.extendDs], + [...RecSpace.measures, ...RecSpace.extendMs].map(m => m.key), + usePredicates ? RecSpace.predicates : null, + mergedMeasures.map((m) => ({ + op: m.op, + field: m.key, + as: m.key, + })), + fieldsWithType as any, + RecSpace.type as IReasonType, + true, + true + ); + if (container.current) { + embed(container.current, _vegaSpec); + } + } + }, [visIndex, recSpaces, visSpaces, fieldsWithType, dataSource]) + + const FilterDesc = useMemo(() => { + if (filters) { + const dimValues = Object.keys(filters) + .filter(k => filters[k].length > 0) + .map((k) => { + return `${k}=${filters[k]}`; + }); + return `选中对象${dimValues.join(', ')}的数据`; + } + return '' + }, [filters]) + + const valueDesc = useMemo(() => { + const meaStatus = valueExp.map( + (mea) => `${mea.key}(${mea.op})的取值${mea.score === 1 ? '大于' : '小于'}预期` + ); + return meaStatus.join(', '); + }, [valueExp]) + + return ( +
+

+ {FilterDesc}, {valueDesc} +

+ {loading && ( +
+ )} +
+
+ ({ + value: s.type || '' + i, + label: `${s.type ? ReasonTypeNames[s.type] : '未识别'}: ${ + s.score.toFixed(2) + }`, + }))} + onChange={(v, i) => { + setVisIndex(i); + }} + /> +
+
+
+ {recSpaces[visIndex] && ( +
+ 维度是{recSpaces[visIndex].dimensions.join(', ')}。
+ 度量是{recSpaces[visIndex].measures.map((m) => m.key).join(', ')}。
+ 此时具有 + {recSpaces[visIndex].type + ? ReasonTypeNames[recSpaces[visIndex].type!] + : '未识别'}{' '} + ,评分为{recSpaces[visIndex].score}。 +
+ {recSpaces[visIndex].description && + recSpaces[visIndex].description.intMeasures && + FilterDesc + + recSpaces[visIndex].description.intMeasures + .map( + (mea: any) => + `${mea.key}(${mea.op})}的取值${ + mea.score === 1 ? '大于' : '小于' + }预期` + ) + .join(', ')} +
+ +
+ )} +
+
+
+ ); +} + +export default InsightMainBoard; diff --git a/packages/graphic-walker/src/InsightBoard/radioGroupButtons.tsx b/packages/graphic-walker/src/InsightBoard/radioGroupButtons.tsx index 76345754..49579542 100644 --- a/packages/graphic-walker/src/InsightBoard/radioGroupButtons.tsx +++ b/packages/graphic-walker/src/InsightBoard/radioGroupButtons.tsx @@ -2,8 +2,9 @@ import React from 'react'; import styled from 'styled-components'; const RGBContainer = styled.div` + font-size: 14px; .option { - padding: 1em; + padding: 0.8em; margin: 4px; border: 1px solid #f0f0f0; background-color: #f0f0f0; diff --git a/packages/graphic-walker/src/components/container.tsx b/packages/graphic-walker/src/components/container.tsx index 1910359d..03b7454a 100644 --- a/packages/graphic-walker/src/components/container.tsx +++ b/packages/graphic-walker/src/components/container.tsx @@ -5,4 +5,11 @@ export const Container = styled.div` padding: 1em; margin: 1em; background-color: #fff; -`; \ No newline at end of file +`; + +export const NestContainer = styled.div` + border: 1px solid #d9d9d9; + padding: 0.4em; + margin: 0.2em; + background-color: #fff; +` \ No newline at end of file diff --git a/packages/graphic-walker/src/components/modal.tsx b/packages/graphic-walker/src/components/modal.tsx index 87e37a16..755693a2 100644 --- a/packages/graphic-walker/src/components/modal.tsx +++ b/packages/graphic-walker/src/components/modal.tsx @@ -8,7 +8,8 @@ const Container = styled.div` > div.header { background-color: #f0f0f0; display: flex; - padding: 8px; + padding: 12px; + font-size: 14px; align-items: center; } > div.container { @@ -19,7 +20,7 @@ const Container = styled.div` top: 50%; transform: translate(-50%, -50%); background-color: #fff; - box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); + /* box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); */ border-radius: 4px; z-index: 999; `; @@ -30,7 +31,7 @@ interface ModalProps { const Modal: React.FC = props => { const { onClose, title } = props; return ( - +
{title} = props => { const { preWorkDone } = props; - const store = useGlobalStore(); + const { commonStore } = useGlobalStore(); - const { currentDataset, datasets, showDSPanel } = store; + const { currentDataset, datasets, showDSPanel } = commonStore; return {!preWorkDone &&
} @@ -21,7 +22,7 @@ const DataSourceSegment: React.FC = props => { {showDSPanel && ( { store.setShowDSPanel(false) }} + onClose={() => { commonStore.setShowDSPanel(false) }} > )} - {preWorkDone && iready} + { preWorkDone && } + { !preWorkDone && }
} diff --git a/packages/graphic-walker/src/dataSource/pannel.tsx b/packages/graphic-walker/src/dataSource/pannel.tsx index a1cbfd18..84a30185 100644 --- a/packages/graphic-walker/src/dataSource/pannel.tsx +++ b/packages/graphic-walker/src/dataSource/pannel.tsx @@ -14,11 +14,11 @@ interface DSPanelProps { } const DataSourcePanel: React.FC = props => { const fileRef = useRef(null); - const store = useGlobalStore(); - const { tmpDSName, tmpDataSource } = store; + const { commonStore } = useGlobalStore(); + const { tmpDSName, tmpDataSource } = commonStore; const onSubmitData = useCallback(() => { - store.commitTempDS(); + commonStore.commitTempDS(); }, []) return ( @@ -35,18 +35,18 @@ const DataSourcePanel: React.FC = props => { config: { type: 'reservoirSampling', size: Infinity }, onLoading: () => {} }).then((data) => { - store.updateTempDS(data as Record[]); + commonStore.updateTempDS(data as Record[]); }); } }} /> -
+
-
{field.key}({field.dataType})
- { +
@@ -69,12 +90,12 @@ const Table: React.FC = props => { {tmpDSRawFields.map((field, fIndex) => (
- {field.key}({field.dataType}) + {field.key}
{ + commonStore.setVisualConfig('defaultAggregated', e.target.checked); + }} /> + +
+
+ + +
+ + +} + +export default observer(VisualSettings); \ No newline at end of file diff --git a/packages/graphic-walker/tailwind.config.js b/packages/graphic-walker/tailwind.config.js index db7db016..430e63e0 100644 --- a/packages/graphic-walker/tailwind.config.js +++ b/packages/graphic-walker/tailwind.config.js @@ -10,7 +10,9 @@ module.exports = { extend: {}, }, variants: { - extend: {}, + extend: { + 'backgroundColor': ['disabled'] + }, }, plugins: [], } From 8c6a440ffbac1ceeda4b2fd3c3545079cb2cdfe0 Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Fri, 24 Sep 2021 18:55:37 +0800 Subject: [PATCH 09/10] feat: allow drop to delete fields --- packages/graphic-walker/src/App.tsx | 24 +- .../src/Fields/fieldsContext.tsx | 1 + packages/graphic-walker/src/Fields/index.tsx | 259 ------------------ .../src/Fields/useFieldsState.tsx | 40 --- .../src/store/visualSpecStore.ts | 15 +- 5 files changed, 16 insertions(+), 323 deletions(-) delete mode 100644 packages/graphic-walker/src/Fields/index.tsx delete mode 100644 packages/graphic-walker/src/Fields/useFieldsState.tsx diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index b519e1cf..d22bea18 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -1,7 +1,5 @@ import React, { useState, useEffect, useCallback } from 'react'; -import DraggableFields from './Fields'; import { Record, Filters, Field, IMutField } from './interfaces'; -import ReactVega from './vis/react-vega'; import VisualSettings from './visualSettings'; import { Container, NestContainer } from './components/container'; import ClickMenu from './components/clickMenu'; @@ -10,8 +8,6 @@ import PosFields from './Fields/posFields'; import AestheticFields from './Fields/AestheticFields'; import DatasetFields from './Fields/DatasetFields'; import ReactiveRenderer from './renderer/index'; -import { useFieldsState } from './Fields/useFieldsState' -import Modal from './components/modal'; import DataSourceSegment from './dataSource/index'; import { useGlobalStore } from './store'; import { preAnalysis, destroyWorker } from './services' @@ -25,18 +21,10 @@ export interface EditorProps { const App: React.FC = props => { const { dataSource = [], rawFields = [] } = props; - const { commonStore, vizStore } = useGlobalStore(); - // const [fields, setFields] = useState([]); - // const [geomType, setGeomType] = useState(GEMO_TYPES[0].value); - // const [aggregated, setAggregated] = useState(true); - // const [position, setPosition] = useState<[number, number]>([0, 0]); - // const [showMenu, setShowMenu] = useState(false); - // const [showInsight, setShowInsight] = useState(false); - // const [filters, setFilters] = useState({}); + const { commonStore } = useGlobalStore(); const [insightReady, setInsightReady] = useState(true); - const { currentDataset, datasets, filters, vizEmbededMenu, showInsightBoard } = commonStore; - const { viewDimensions, viewMeasures, draggableFieldState } = vizStore + const { currentDataset, datasets, vizEmbededMenu } = commonStore; // use as an embeding module, use outside datasource from props. useEffect(() => { @@ -69,14 +57,6 @@ const App: React.FC = props => { return (
- {/* - { - setFstate(state) - }} - fields={fields} - /> - */}
diff --git a/packages/graphic-walker/src/Fields/fieldsContext.tsx b/packages/graphic-walker/src/Fields/fieldsContext.tsx index f5b81a9b..b99c8933 100644 --- a/packages/graphic-walker/src/Fields/fieldsContext.tsx +++ b/packages/graphic-walker/src/Fields/fieldsContext.tsx @@ -29,6 +29,7 @@ export const FieldsContextWrapper: React.FC = props => { const { vizStore } = useGlobalStore(); const onDragEnd = useCallback((result: DropResult, provided: ResponderProvided) => { if (!result.destination) { + vizStore.removeField(result.source.droppableId as keyof DraggableFieldState, result.source.index) return; } const destination = result.destination as DraggableLocation; diff --git a/packages/graphic-walker/src/Fields/index.tsx b/packages/graphic-walker/src/Fields/index.tsx deleted file mode 100644 index ba7a04f1..00000000 --- a/packages/graphic-walker/src/Fields/index.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { - DragDropContext, - Droppable, - Draggable, - DropResult, - ResponderProvided, - DraggableLocation, -} from "react-beautiful-dnd"; -import styled from "styled-components"; -import produce from "immer"; -import { FieldListContainer, FieldsContainer, Pill } from "./components"; -import { move, reorder } from "./utils"; -import { Field } from "../interfaces"; -import { Container } from '../components/container' - -const RootContainer = styled.div` - font-size: 12px; -`; - -const aggregatorList = [ - { - value: "sum", - label: "求和", - }, - { - value: "mean", - label: "平均值", - }, - { - value: "count", - label: "计数", - }, -]; - -export interface DraggableFieldState { - fields: Field[]; - rows: Field[]; - columns: Field[]; - color: Field[]; - opacity: Field[]; - size: Field[]; -} - -// const draggableStateKeys = Object.keys(initDraggableState) as Array< -// keyof DraggableFieldState -// >; - -const draggableStateKeys: Array<{ - id: keyof DraggableFieldState; - name: string; - mode: number -}> = [ - { id: 'fields', name: '字段', mode: 0 }, - { id: 'columns', name: '列', mode: 0 }, - { id: 'rows', name: '行', mode: 0 }, - { id: 'color', name: '颜色', mode: 1 }, - { id: 'opacity', name: '透明度', mode: 1 }, - { id: 'size', name: '大小', mode: 1 }, -]; - - -interface DraggableFieldsProps { - fields: Field[]; - onStateChange?: (state: DraggableFieldState) => void; -} - -const DraggableFields: React.FC = (props) => { - const { fields = [], onStateChange } = props; - const [state, setState] = useState({ - fields: [], - rows: [], - columns: [], - color: [], - opacity: [], - size: [] - }); - useEffect(() => { - setState({ - fields, - rows: [], - columns: [], - color: [], - opacity: [], - size: [], - }); - }, [fields]); - useEffect(() => { - if (onStateChange) { - onStateChange(state); - } - }, [state, onStateChange]); - const onDragEnd = useCallback( - (result: DropResult, provided: ResponderProvided) => { - if (!result.destination) { - return; - } - const destination = result.destination as DraggableLocation; - if (destination.droppableId === result.source.droppableId) { - if (destination.index === result.source.index) return; - setState((state) => { - let listKey = destination - .droppableId as keyof DraggableFieldState; - let newList = reorder( - state[listKey], - result.source.index, - destination.index - ); - return { - ...state, - [listKey]: newList, - }; - }); - } else { - setState((state) => { - let sourceKey = result.source - .droppableId as keyof DraggableFieldState; - let targetKey = destination - .droppableId as keyof DraggableFieldState; - let { originList, targetList } = move( - state[sourceKey], - result.source.index, - state[targetKey], - destination.index - ); - return { - ...state, - [sourceKey]: originList, - [targetKey]: targetList, - }; - }); - } - }, - [setState] - ); - return ( - - - {draggableStateKeys - .filter((d) => d.mode === 0) - .map((dkey) => ( - - - {(provided, snapshot) => ( - - {state[dkey.id].map((f, index) => ( - - {(provided, snapshot) => { - return ( - - {f.name}  - {f.type === 'M' && ( - - )} - - ); - }} - - ))} - - )} - - - ))} -
- - {draggableStateKeys - .filter((d) => d.mode === 1) - .map((dkey) => ( - - - {(provided, snapshot) => ( - - {state[dkey.id].map((f, index) => ( - - {(provided, snapshot) => { - return ( - - {f.name}  - {f.type === 'M' && ( - - )} - - ); - }} - - ))} - - )} - - - ))} - -
-
-
- ); -}; - -export default DraggableFields; diff --git a/packages/graphic-walker/src/Fields/useFieldsState.tsx b/packages/graphic-walker/src/Fields/useFieldsState.tsx deleted file mode 100644 index e77db6b1..00000000 --- a/packages/graphic-walker/src/Fields/useFieldsState.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import { Field } from '../interfaces'; -import { DraggableFieldState } from './index'; - -const INIT_DF_STATE: DraggableFieldState = { - fields: [], - rows: [], - columns: [], - color: [], - opacity: [], - size: [], - }; - -export function useFieldsState() { - const [fstate, setFstate] = useState(INIT_DF_STATE); - const viewDimensions = useMemo(() => { - return [ - ...fstate.rows, - ...fstate.columns, - ...fstate.color, - ...fstate.opacity, - ...fstate.size - ].filter(f => f.type === 'D'); - }, [fstate]) - const viewMeasures = useMemo(() => { - return [ - ...fstate.rows, - ...fstate.columns, - ...fstate.color, - ...fstate.opacity, - ...fstate.size, - ].filter((f) => f.type === 'M'); - }, [fstate]); - return { - fstate, - setFstate, - viewDimensions, - viewMeasures - } -} \ No newline at end of file diff --git a/packages/graphic-walker/src/store/visualSpecStore.ts b/packages/graphic-walker/src/store/visualSpecStore.ts index d16f30d6..ed6237ae 100644 --- a/packages/graphic-walker/src/store/visualSpecStore.ts +++ b/packages/graphic-walker/src/store/visualSpecStore.ts @@ -68,14 +68,25 @@ export class VizSpecStore { } } public reorderField(stateKey: keyof DraggableFieldState, sourceIndex: number, destinationIndex: number) { + if (stateKey === 'fields') return; if (sourceIndex === destinationIndex) return; const fields = this.draggableFieldState[stateKey]; const [field] = fields.splice(sourceIndex, 1); fields.splice(destinationIndex, 0, field); } public moveField(sourceKey: keyof DraggableFieldState, sourceIndex: number, destinationKey: keyof DraggableFieldState, destinationIndex: number) { - const [field] = this.draggableFieldState[sourceKey].splice(sourceIndex, 1); - this.draggableFieldState[destinationKey].splice(destinationIndex, 1, field) + let movingField: IViewField; + if (sourceKey === 'fields') { + movingField = this.draggableFieldState[sourceKey][sourceIndex] + } else { + [movingField] = this.draggableFieldState[sourceKey].splice(sourceIndex, 1); + } + if (destinationKey === 'fields')return; + this.draggableFieldState[destinationKey].splice(destinationIndex, 1, movingField) + } + public removeField(sourceKey: keyof DraggableFieldState, sourceIndex: number) { + if (sourceKey === 'fields')return; + this.draggableFieldState[sourceKey].splice(sourceIndex, 1); } public setFieldAggregator (stateKey: keyof DraggableFieldState, index: number, aggName: string) { const fields = this.draggableFieldState[stateKey] From 027e5ec257250e2f72709e10e79537504286035b Mon Sep 17 00:00:00 2001 From: Hao Chen <270001151@qq.com> Date: Fri, 24 Sep 2021 19:15:52 +0800 Subject: [PATCH 10/10] fix: graphic-walker css import in rath --- packages/frontend/src/pages/visualInterface/index.tsx | 1 + packages/graphic-walker/README.md | 1 + packages/graphic-walker/src/App.tsx | 6 ++++-- packages/graphic-walker/src/index.tsx | 2 -- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/pages/visualInterface/index.tsx b/packages/frontend/src/pages/visualInterface/index.tsx index 287173d4..c14a4dce 100644 --- a/packages/frontend/src/pages/visualInterface/index.tsx +++ b/packages/frontend/src/pages/visualInterface/index.tsx @@ -4,6 +4,7 @@ import { GraphicWalker } from 'graphic-walker'; import { useGlobalStore } from '../../store'; import { useMemo } from 'react'; import { IMutField } from 'graphic-walker/dist/interfaces'; +import 'graphic-walker/dist/style.css'; const VisualInterface: React.FC = props => { const { dataSourceStore } = useGlobalStore(); diff --git a/packages/graphic-walker/README.md b/packages/graphic-walker/README.md index b0a70a43..ce721bac 100644 --- a/packages/graphic-walker/README.md +++ b/packages/graphic-walker/README.md @@ -18,6 +18,7 @@ npm run build In your app: ```typescript import { GraphicWalker } from 'graphic-walker'; +import 'graphic-walker/dist/style.css' const YourEmbeddingTableauStyleApp: React.FC = props => { const { dataSource, fields } = props; diff --git a/packages/graphic-walker/src/App.tsx b/packages/graphic-walker/src/App.tsx index d22bea18..068c82a8 100644 --- a/packages/graphic-walker/src/App.tsx +++ b/packages/graphic-walker/src/App.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { Record, Filters, Field, IMutField } from './interfaces'; +import React, { useState, useEffect } from 'react'; +import { Record, IMutField } from './interfaces'; import VisualSettings from './visualSettings'; import { Container, NestContainer } from './components/container'; import ClickMenu from './components/clickMenu'; @@ -13,6 +13,8 @@ import { useGlobalStore } from './store'; import { preAnalysis, destroyWorker } from './services' import { observer } from 'mobx-react-lite'; import { toJS } from 'mobx'; +import "tailwindcss/tailwind.css" +import './index.css' export interface EditorProps { dataSource?: Record[]; diff --git a/packages/graphic-walker/src/index.tsx b/packages/graphic-walker/src/index.tsx index 5fcfa563..bd08bd63 100644 --- a/packages/graphic-walker/src/index.tsx +++ b/packages/graphic-walker/src/index.tsx @@ -2,8 +2,6 @@ import React from 'react'; import App, { EditorProps } from './App'; import { StoreWrapper } from './store/index'; import { FieldsContextWrapper } from './Fields/fieldsContext'; -import "tailwindcss/tailwind.css" -import './index.css' export const GraphicWalker: React.FC = props => { return