Skip to content

Commit

Permalink
Merge branch 'main' into feat-colorSetting-0825-ldy
Browse files Browse the repository at this point in the history
  • Loading branch information
islxyqwe committed Sep 26, 2023
2 parents 4ef5da2 + 915b961 commit 4663416
Show file tree
Hide file tree
Showing 70 changed files with 3,060 additions and 662 deletions.
60 changes: 55 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,26 @@ Graphic Walker is a different open-source alternative to Tableau. It allows data

### Why is it different?

It is extremely easy to embed in your apps just as a component 🎉! The original purpose of graphic-walker is not to be a heavy BI platform, but a easy to embed, lite, plugin.
It is extremely easy to embed in your apps just as a React component 🎉! The original purpose of graphic-walker is not to be a heavy BI platform, but a easy to embed, lite, plugin.

### Main features
+ A user friendly drag and drop based interaction for exploratory data analysis with visualizations.
+ A Data Explainer which explains why some patterns occur / what may cause them (like salesforce einstein).
+ Using web workers to handle computational tasks which allow you to use it as a pure front-end app.
+ Graphic Walker now supports Dark Theme! 🤩
+ Spatial visualization.
+ A general query interface for submit data queries to your own computation service. You can have a look at how we using DuckDB to handle data queries in [PyGWalker](https://github.com/kanaries/pygwalker)
+ Light Theme / Dark Theme! 🤩
+ Spatial visualization. (supports GeoJSON, TopoJSON)
+ Natural language / Chat interface. Ask question about your data!
+ A grammar of graphics based visual analytic user interface where users can build visualizations from low-level visual channel encodings. (based on [vega-lite](https://vega.github.io/vega-lite/))

https://github.com/Kanaries/graphic-walker/assets/22167673/15d34bed-9ccc-42da-a2f4-9859ea36fa65

> Graphic Walker is a lite visual analytic component. If you are interested in more advanced data analysis software, check our related project [RATH](https://github.com/Kanaries/Rath), an augmented analytic BI with automated insight discovery, causal analysis and visualization auto generation based on human's visual perception.

## Usage for End Users

First, upload your Data(csv/json) file, preview your data, and define the analytic type of columns (dimension or measure).

> We are developing more types of data sources. You are welcome to raise an issue telling us the types of sources you are using. If you are a developer, graphic-walker can be used as an embedding component, and you can pass your parsed data source to it. For example, [Rath](https://github.com/Kanaries/Rath) uses graphic-walker as an embedding components, and it supports many common data sources. You can load your data in [Rath](https://github.com/Kanaries/Rath) and bring the data into graphic-walker. In this way, users can also benefit from data cleaning and transformation features in [Rath](https://github.com/Kanaries/Rath).
> We are developing more types of data sources. You are welcome to raise an issue telling us the types of sources you are using. If you are a developer, graphic-walker can be used as an embedding component, and you can pass your parsed data source to it. For example, [Rath](https://github.com/Kanaries/Rath) uses graphic-walker as an embeded component, and it supports many common data sources. You can load your data in [Rath](https://github.com/Kanaries/Rath) and bring the data into graphic-walker. In this way, users can also benefit from data cleaning and transformation features in [Rath](https://github.com/Kanaries/Rath).
![graphic walker dataset upload](https://docs-us.oss-us-west-1.aliyuncs.com/images/graphic-walker/20230811/gw-create-ds.png)

Expand Down Expand Up @@ -296,6 +296,56 @@ When you are using Server-side computation, you should provide `rawFields` toget

Customize the toolbar.

#### `channelScales`: optional _{ [`IChannelScales`](./packages/graphic-walker/src/interfaces.ts) }_

Customize the scale of color, opacity, and size channel.
see [Vega Docs](https://vega.github.io/vega/docs/schemes/#reference) for available color schemes.

Here are some examples:
```ts
// use a another color pattren
const channelScales = {
color: {
scheme: "tableau10"
}
}
// use a diffrent color pattren in dark mode and light mode
const channelScales = {
color({theme}) {
if(theme === 'dark') {
return {
scheme: 'darkblue'
}
}else {
return {
scheme: 'lightmulti'
}
}
}
}
// use a custom color palette
const channelScales = {
color: {
range: ['red', 'blue', '#000000']
}
}
// customing opacity
const channelScales = {
// map value of 0 - 255 to opacity 0 - 1
opacity: {
range: [0, 1],
domain: [0, 255],
}
}
// set min radius for arc chart
const channelScales = {
radius: {
rangeMin: 20
}
}

```

### Ref

```ts
Expand Down
3 changes: 2 additions & 1 deletion computation.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,10 @@ type IExpParameter = (
);

interface IExpression {
op: 'bin' | 'log2' | 'log10' | 'one' | 'binCount';
op: 'bin' | 'one' | 'binCount'|'log';
params: IExpParameter[];
as: string;
num?:number;
}

interface ITransformField {
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"tauri": "tauri"
},
"dependencies": {
"@kanaries/graphic-walker": "0.4.3",
"@kanaries/graphic-walker": "0.4.13",
"@tauri-apps/api": "^1.1.0",
"react": "^17.x",
"react-dom": "^17.x"
Expand Down
5 changes: 4 additions & 1 deletion packages/graphic-walker/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kanaries/graphic-walker",
"version": "0.4.3",
"version": "0.4.13",
"scripts": {
"dev:front_end": "vite --host",
"dev": "npm run dev:front_end",
Expand Down Expand Up @@ -40,6 +40,7 @@
"@kanaries/web-data-loader": "^0.1.7",
"@tailwindcss/forms": "^0.5.4",
"autoprefixer": "^10.3.5",
"canvas-size": "^1.2.6",
"d3-format": "^3.1.0",
"d3-scale": "^4.0.2",
"d3-time-format": "^4.1.0",
Expand All @@ -54,6 +55,8 @@
"postinstall-postinstall": "^2.1.0",
"re-resizable": "^6.9.8",
"react-color": "^2.19.3",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.11",
"react-i18next": "^11.18.6",
"react-leaflet": "^4.2.1",
"react-shadow": "^20.0.0",
Expand Down
62 changes: 48 additions & 14 deletions packages/graphic-walker/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useRef, useMemo, useState } from 'react';
import React, { useEffect, useRef, useMemo, useState, useCallback } from 'react';
import { observer } from 'mobx-react-lite';
import { useTranslation } from 'react-i18next';
import { IGeographicData, IComputationFunction, IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey, Specification } from './interfaces';
import { IGeographicData, IComputationFunction, IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey, Specification, IGeoDataItem, VegaGlobalConfig, IChannelScales } from './interfaces';
import type { IReactVegaHandler } from './vis/react-vega';
import VisualSettings from './visualSettings';
import PosFields from './fields/posFields';
Expand All @@ -19,10 +19,20 @@ import DatasetConfig from './dataSource/datasetConfig';
import { useCurrentMediaTheme } from './utils/media';
import CodeExport from './components/codeExport';
import VisualConfig from './components/visualConfig';
import ExplainData from './components/explainData';
import GeoConfigPanel from './components/leafletRenderer/geoConfigPanel';
import type { ToolbarItemProps } from './components/toolbar';
import ClickMenu from './components/clickMenu';
import {
LightBulbIcon,
} from "@heroicons/react/24/outline";
import AskViz from './components/askViz';
import { getComputation } from './computation/clientComputation';
import LogPanel from './fields/datasetFields/logPanel';
import BinPanel from './fields/datasetFields/binPanel';
import { ErrorContext } from './utils/reportError';
import { ErrorBoundary } from "react-error-boundary";
import Errorpanel from './components/errorpanel';

export interface IGWProps {
dataSource?: IRow[];
Expand All @@ -38,6 +48,7 @@ export interface IGWProps {
fieldKeyGuard?: boolean;
/** @default "vega" */
themeKey?: IThemeKey;
themeConfig?: VegaGlobalConfig;
dark?: IDarkMode;
storeRef?: React.MutableRefObject<IGlobalStore | null>;
computation?: IComputationFunction;
Expand All @@ -55,6 +66,9 @@ export interface IGWProps {
}
};
computationTimeout?: number;
onError?: (err: Error) => void;
geoList?: IGeoDataItem[];
channelScales?: IChannelScales;
}

const App = observer<IGWProps>(function App(props) {
Expand All @@ -67,6 +81,7 @@ const App = observer<IGWProps>(function App(props) {
hideDataSourceConfig,
fieldKeyGuard = true,
themeKey = 'vega',
themeConfig,
dark = 'media',
computation,
toolbar,
Expand All @@ -76,7 +91,7 @@ const App = observer<IGWProps>(function App(props) {
} = props;
const { commonStore, vizStore } = useGlobalStore();

const { datasets, segmentKey } = commonStore;
const { datasets, segmentKey, vizEmbededMenu } = commonStore;

const { t, i18n } = useTranslation();
const curLang = i18n.language;
Expand Down Expand Up @@ -159,7 +174,20 @@ const App = observer<IGWProps>(function App(props) {

const rendererRef = useRef<IReactVegaHandler>(null);

const downloadCSVRef = useRef<{ download: () => void }>({download() {}});

const reportError = useCallback((msg: string, code?: number) => {
const err = new Error(`Error${code ? `(${code})`: ''}: ${msg}`);
console.error(err);
props.onError?.(err);
if (code) {
commonStore.updateShowErrorResolutionPanel(code);
}
}, [props.onError]);

return (
<ErrorContext value={{reportError}}>
<ErrorBoundary fallback={<div>Something went wrong</div>} onError={props.onError} >
<div
className={`${
darkMode === 'dark' ? 'dark' : ''
Expand All @@ -182,10 +210,14 @@ const App = observer<IGWProps>(function App(props) {
{enhanceAPI?.features?.askviz && (
<AskViz api={typeof enhanceAPI.features.askviz === 'string' ? enhanceAPI.features.askviz : ''} headers={enhanceAPI?.header} />
)}
<VisualSettings rendererHandler={rendererRef} darkModePreference={dark} exclude={toolbar?.exclude} extra={toolbar?.extra} />
<VisualSettings csvHandler={downloadCSVRef} rendererHandler={rendererRef} darkModePreference={dark} exclude={toolbar?.exclude} extra={toolbar?.extra} />
<CodeExport />
<ExplainData themeKey={themeKey} dark={darkMode}/>
<VisualConfig />
<GeoConfigPanel />
<Errorpanel />
<LogPanel />
<BinPanel/>
{commonStore.showGeoJSONConfigPanel && <GeoConfigPanel geoList={props.geoList} />}
<div className="md:grid md:grid-cols-12 xl:grid-cols-6">
<div className="md:col-span-3 xl:col-span-1">
<DatasetFields />
Expand All @@ -201,17 +233,17 @@ const App = observer<IGWProps>(function App(props) {
<div
className="m-0.5 p-1 border border-gray-200 dark:border-gray-700"
style={{ minHeight: '600px', overflow: 'auto' }}
// onMouseLeave={() => {
// vizEmbededMenu.show && commonStore.closeEmbededMenu();
// }}
// onClick={() => {
// vizEmbededMenu.show && commonStore.closeEmbededMenu();
// }}
onMouseLeave={() => {
vizEmbededMenu.show && commonStore.closeEmbededMenu();
}}
onClick={() => {
vizEmbededMenu.show && commonStore.closeEmbededMenu();
}}
>
{datasets.length > 0 && (
<ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} computationFunction={vizStore.computationFunction} />
<ReactiveRenderer csvRef={downloadCSVRef} ref={rendererRef} themeKey={themeKey} themeConfig={themeConfig} dark={dark} computationFunction={vizStore.computationFunction} channelScales={props.channelScales} />
)}
{/* {vizEmbededMenu.show && (
{vizEmbededMenu.show && (
<ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
<div
className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
Expand All @@ -226,7 +258,7 @@ const App = observer<IGWProps>(function App(props) {
<LightBulbIcon className="ml-1 w-3 flex-grow-0 flex-shrink-0" />
</div>
</ClickMenu>
)} */}
)}
</div>
</div>
</div>
Expand All @@ -242,6 +274,8 @@ const App = observer<IGWProps>(function App(props) {
)}
</div>
</div>
</ErrorBoundary>
</ErrorContext>
);
});

Expand Down
48 changes: 48 additions & 0 deletions packages/graphic-walker/src/components/actionMenu/a11y.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { type HTMLAttributes, type MouseEvent, useCallback, useMemo, type KeyboardEvent, type AriaAttributes, type ComponentPropsWithoutRef } from "react";


export type ReactTag = keyof JSX.IntrinsicElements;
export type ElementType<T extends ReactTag> = Parameters<NonNullable<ComponentPropsWithoutRef<T>['onClick']>>[0] extends MouseEvent<infer U, globalThis.MouseEvent> ? U : never;

interface IUseMenuButtonOptions {
"aria-expanded": NonNullable<AriaAttributes['aria-expanded']>;
onPress?: () => void;
disabled?: boolean;
}

export const useMenuButton = <T extends ReactTag>(options: IUseMenuButtonOptions & Omit<ComponentPropsWithoutRef<T>, keyof IUseMenuButtonOptions>): HTMLAttributes<ElementType<T>> => {
const { ["aria-expanded"]: expanded, onPress, disabled, className = '', ...attrs } = options;

const onClick = useCallback((e: MouseEvent<any>) => {
e.preventDefault();
e.stopPropagation();
onPress?.();
}, [onPress]);

const onKeyDown = useCallback((e: KeyboardEvent<any>) => {
switch (e.key) {
case 'Enter':
case 'Space': {
onPress?.();
break;
}
default: {
return;
}
}
e.preventDefault();
e.stopPropagation();
}, [onPress]);

return useMemo(() => ({
...attrs,
className: `${className} text-gray-500 dark:text-gray-400 ${disabled ? 'cursor-default text-opacity-50' : 'cursor-pointer rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-indigo-500 hover:bg-indigo-100/20 dark:hover:bg-indigo-800/20 hover:text-indigo-700 dark:hover:text-indigo-200'}`,
role: 'button',
"aria-haspopup": 'menu',
"aria-expanded": expanded,
"aria-disabled": disabled,
tabIndex: disabled ? undefined : 0,
onClick,
onKeyDown,
}) as HTMLAttributes<ElementType<T>>, [onClick, onKeyDown, expanded, disabled, className, attrs]);
};
Loading

0 comments on commit 4663416

Please sign in to comment.