Skip to content

Commit

Permalink
Feat binconfig 0818 ldy (#154)
Browse files Browse the repository at this point in the history
* feat: context menu

* feat(menu): common menu items

* feat: add custom bin config

---------

Co-authored-by: AntoineYANG <dev.bob@kanaries.net>
Co-authored-by: islxyqwe <islxyqwe123@gmail.com>
  • Loading branch information
3 people authored Sep 1, 2023
1 parent d47707f commit c6e5b66
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 18 deletions.
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
4 changes: 4 additions & 0 deletions packages/graphic-walker/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
} 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';

export interface IGWProps {
dataSource?: IRow[];
Expand Down Expand Up @@ -193,6 +195,8 @@ const App = observer<IGWProps>(function App(props) {
<CodeExport />
<ExplainData themeKey={themeKey} dark={darkMode}/>
<VisualConfig />
<LogPanel />
<BinPanel/>
{commonStore.showGeoJSONConfigPanel && <GeoConfigPanel />}
<div className="md:grid md:grid-cols-12 xl:grid-cols-6">
<div className="md:col-span-3 xl:col-span-1">
Expand Down
76 changes: 76 additions & 0 deletions packages/graphic-walker/src/components/smallModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useRef } from "react";
import styled from "styled-components";
import { XCircleIcon } from "@heroicons/react/24/outline";
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/outline";

const Background = styled.div`
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
backdrop-filter: blur(1px);
z-index: 25535;
`;

const Container = styled.div`
width: 360px;
max-height: 800px;
> div.container {
padding: 0.5em 1em 1em 1em;
}
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
/* box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); */
border-radius: 4px;
z-index: 999;
`;
interface ModalProps {
onClose?: () => void;
show?: boolean;
title?: string;
}
const Modal: React.FC<ModalProps> = (props) => {
const { onClose, title, show } = props;
const prevMouseDownTimeRef = useRef(0);
return (
<Background
// This is a safer replacement of onClick handler.
// onClick also happens if the click event is begun by pressing mouse button
// at a different element and then released when the mouse is moved on the target element.
// This case is required to be prevented, especially disturbing when interacting
// with a Slider component.
className={"border border-gray-300 dark:border-gray-600 " + (show ? "block" : "hidden")}
onMouseDown={() => (prevMouseDownTimeRef.current = Date.now())}
onMouseOut={() => (prevMouseDownTimeRef.current = 0)}
onMouseUp={() => {
if (Date.now() - prevMouseDownTimeRef.current < 1000) {
onClose?.();
}
}}
>
<Container role="dialog" className="bg-white dark:bg-zinc-900 shadow-lg rounded-md border border-gray-100 dark:border-gray-800" onMouseDown={(e) => e.stopPropagation()}>
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button
type="button"
className="rounded-md bg-white dark:bg-zinc-900 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
onClick={() => {
onClose?.();
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="px-6 pt-4 text-base font-semibold leading-6 text-gray-900 dark:text-gray-50">{title}</div>
<div className="container">{props.children}</div>
</Container>
</Background>
);
};

export default Modal;
95 changes: 95 additions & 0 deletions packages/graphic-walker/src/fields/datasetFields/binPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { observer } from 'mobx-react-lite';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useGlobalStore } from '../../store';
import SmallModal from '../../components/smallModal';
import PrimaryButton from '../../components/button/primary';
import DefaultButton from '../../components/button/default';
import { useTranslation } from 'react-i18next';
import { ICreateField } from '../../interfaces';

const FieldScalePanel: React.FC = (props) => {
const { commonStore, vizStore } = useGlobalStore();
const { showBinSettingPanel, setShowBinSettingPanel } = commonStore;
const { t } = useTranslation();
const [chosenOption, setChosenOption] = useState<'widths' | 'counts'>('widths');
const [value, setValue] = useState<string>('');
const options = ['widths', 'counts'];


useEffect(() => {
setChosenOption('widths');
setValue('');
},[showBinSettingPanel]);

return (
<SmallModal
show={showBinSettingPanel}
onClose={() => {
commonStore.setShowBinSettingPanel(false);
}}
>
<div className="flex flex-col justify-center items-start">
<h2 className="font-medium text-xl">Bins</h2>
<p className="font-normal">Set bin config for field</p>
<fieldset className="mt-2 gap-1 flex flex-col justify-center items-start">
{options.map((option,index) => {
return (
<div key={index}>
<div className="flex my-2" key={option}>
<div className="align-top">
<input
type="radio"
className="h-4 w-4 border-gray-300 text-black focus:ring-black"
id={option}
checked={option === chosenOption}
onChange={(e) => setChosenOption(option as typeof chosenOption)}
/>
</div>
<div className="ml-3">
<label htmlFor={option}>{`Bin with equal ${option}`}</label>
</div>
</div>
{chosenOption === option && (<div className="flex items-center space-x-2">
<label className="text-ml whitespace-nowrap">Bin number</label>
<input
type="text"
className="block w-full text-gray-700 dark:text-gray-200 rounded-md border-0 py-1 px-2 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-zinc-900 "
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
</div>)}
</div>
);
})}


<div className='mt-2'>
<PrimaryButton
// text={t("actions.confirm")}
className="mr-2 px-2 "
text="Confirm"
onClick={() => {
const field = commonStore.createField as ICreateField;
console.log(field,value);
vizStore.createBinField(field.channel, field.index, chosenOption === 'widths'?'bin':'binCount', Number(value));
commonStore.setShowBinSettingPanel(false);
return;
}}
/>
<DefaultButton
text={t("actions.cancel")}
className="mr-2 px-2"
onClick={() => {
commonStore.setShowBinSettingPanel(false);
return;
}}
/></div>
</fieldset>
</div>
</SmallModal>

);
};
export default observer(FieldScalePanel);
67 changes: 67 additions & 0 deletions packages/graphic-walker/src/fields/datasetFields/logPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { observer } from 'mobx-react-lite';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useGlobalStore } from '../../store';
import SmallModal from '../../components/smallModal';
import PrimaryButton from '../../components/button/primary';
import DefaultButton from '../../components/button/default';
import { useTranslation } from 'react-i18next';
import { runInAction, toJS } from 'mobx';
import { ICreateField, IViewField } from '../../interfaces';
import { useRenderer } from '../../renderer/hooks';
import { applyViewQuery, transformDataService } from '../../services';

const FieldScalePanel: React.FC = (props) => {
const { commonStore, vizStore } = useGlobalStore();
const { showLogSettingPanel } = commonStore;
const [baseNum, setBaseNum] = useState<string>('');
const { t } = useTranslation();
useEffect(() => {
setBaseNum('');
}, [showLogSettingPanel]);
return (
<SmallModal
show={showLogSettingPanel}
onClose={() => {
commonStore.setShowLogSettingPanel(false);
}}
>
<div className="flex flex-col justify-center items-start">
<h2 className="font-medium text-xl my-2">Logarithm Transformation</h2>
<p className="font-normal ">Set log config for field</p>
<fieldset className="mt-2 gap-1 flex flex-col justify-center items-start">
<div className="flex items-center space-x-2">
<label className="text-ml whitespace-nowrap">Logarithmic base</label>
<input
type="text"
value={baseNum}
className="block w-full text-gray-700 dark:text-gray-200 rounded-md border-0 py-1 px-2 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-zinc-900 "
onChange={(e) => {
setBaseNum(e.target.value);
}}
/>
</div>
<div className="mt-2">
<PrimaryButton
text={t("actions.confirm")}
className="mr-2 px-2 "
// text="Confirm"
onClick={() => {
const field = commonStore.createField as ICreateField;
vizStore.createLogField(field.channel, field.index, 'log', Number(baseNum));
commonStore.setShowLogSettingPanel(false);
}}
/>
<DefaultButton
text={t('actions.cancel')}
className="mr-2 px-2"
onClick={() => {
commonStore.setShowLogSettingPanel(false);
}}
/>
</div>
</fieldset>
</div>
</SmallModal>
);
};
export default observer(FieldScalePanel);
24 changes: 21 additions & 3 deletions packages/graphic-walker/src/fields/datasetFields/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { useTranslation } from "react-i18next";
import { useGlobalStore } from "../../store";
import type { IActionMenuItem } from "../../components/actionMenu/list";
import { COUNT_FIELD_ID, DATE_TIME_DRILL_LEVELS } from "../../constants";
import { CommonStore } from "../../store/commonStore";


const keepTrue = <T extends string | number | object | Function | symbol>(array: (T | 0 | null | false | undefined | void)[]): T[] => {
return array.filter(Boolean) as T[];
};

export const useMenuActions = (channel: "dimensions" | "measures"): IActionMenuItem[][] => {
const { vizStore } = useGlobalStore();
const { vizStore, commonStore } = useGlobalStore();
const fields = vizStore.draggableFieldState[channel];
const { t } = useTranslation('translation', { keyPrefix: "field_menu" });

Expand Down Expand Up @@ -53,16 +54,33 @@ export const useMenuActions = (channel: "dimensions" | "measures"): IActionMenuI
label: "Log10",
disabled: f.semanticType === 'nominal' || f.semanticType === 'ordinal',
onPress() {
vizStore.createLogField(channel, index, "log10");
vizStore.createLogField(channel, index, "log", 10);
},
},
{
label: "Log2",
disabled: f.semanticType === 'nominal' || f.semanticType === 'ordinal',
onPress() {
vizStore.createLogField(channel, index, "log2");
vizStore.createLogField(channel, index, "log", 2);
},
},
{
label:"Log(customize)",
disabled: f.semanticType === 'nominal' || f.semanticType === 'ordinal',
onPress(){
commonStore.setShowLogSettingPanel(true);
commonStore.setCreateField({channel:channel,index:index})
}
},
{
label:"Bin(customize)",
disabled: f.semanticType === 'nominal' || f.semanticType === 'ordinal',
onPress(){
commonStore.setShowBinSettingPanel(true);
commonStore.setCreateField({channel:channel,index:index});
}
},

],
},
(f.semanticType === 'temporal' || isDateTimeDrilled) && {
Expand Down
8 changes: 7 additions & 1 deletion packages/graphic-walker/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export interface Filters {
[key: string]: any[];
}

export interface ICreateField {
channel:"dimensions" | "measures";
index:number;
}

export interface IMutField {
fid: string;
key?: string;
Expand Down Expand Up @@ -83,9 +88,10 @@ export type IExpParamter =
};

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

export type IGeoRole = 'longitude' | 'latitude' | 'none';
Expand Down
25 changes: 19 additions & 6 deletions packages/graphic-walker/src/lib/execExp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export interface IDataFrame {
}

export function execExpression (exp: IExpression, dataFrame: IDataFrame): IDataFrame {
const { op, params } = exp;
const { op, params, num } = exp;
console.log('exo',exp)
const subFrame: IDataFrame = { ...dataFrame };
const len = dataFrame[Object.keys(dataFrame)[0]].length;
for (let param of params) {
Expand All @@ -31,14 +32,16 @@ export function execExpression (exp: IExpression, dataFrame: IDataFrame): IDataF
switch (op) {
case 'one':
return one(exp.as, params, subFrame);
case 'bin':
return bin(exp.as, params, subFrame);
case 'log':
return log(exp.as, params, subFrame, num);
case 'log2':
return log2(exp.as, params, subFrame);
return log(exp.as, params, subFrame, 2);
case 'log10':
return log10(exp.as, params, subFrame);
return log(exp.as, params, subFrame, 10);
case 'binCount':
return binCount(exp.as, params, subFrame);
return binCount(exp.as, params, subFrame, num);
case 'bin':
return bin(exp.as, params, subFrame, num);
case 'dateTimeDrill':
return dateTimeDrill(exp.as, params, subFrame);
default:
Expand Down Expand Up @@ -123,6 +126,16 @@ function log10(resKey: string, params: IExpParamter[], data: IDataFrame): IDataF
}
}

function log(resKey: string, params: IExpParamter[], data: IDataFrame, baseNum: number | undefined=10): IDataFrame {
const { value: fieldKey } = params[0];
const fieldValues = data[fieldKey];
const newField = fieldValues.map((v: number) => Math.log(v) / Math.log(baseNum) );
return {
...data,
[resKey]: newField,
}
}

function one(resKey: string, params: IExpParamter[], data: IDataFrame): IDataFrame {
// const { value: fieldKey } = params[0];
if (Object.keys(data).length === 0) return data;
Expand Down
Loading

1 comment on commit c6e5b66

@vercel
Copy link

@vercel vercel bot commented on c6e5b66 Sep 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.