feat: echarts

This commit is contained in:
dengll 2022-11-29 15:20:56 +08:00
parent f35e575d00
commit 47669c0d9f
54 changed files with 4157 additions and 7 deletions

View File

@ -68,3 +68,6 @@ APPSMITH_RECAPTCHA_ENABLED=
# Intercom
APPSMITH_DISABLE_INTERCOM=
# baidu map
BMAP_AK=

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ app/client/yalc.lock
.idea
.fleet/*
app/client/.fleet/*
*debug.log

View File

@ -1,7 +1,9 @@
# 前端低代码工具base on AppSmith-v1.7.3
## 🎈 启动项目windows
[非windows](/contributions/ClientSetup.md)
[非 windows](/contributions/ClientSetup.md)
```
// 配置 host
127.0.0.1 dev.appsmith.com
@ -14,11 +16,12 @@ cd app/client
yarn start-proxy
// 启动前端服务
yarn
yarn
yarn start-win ( yarn start-cloudos )
```
[服务端指南](/contributions/ServerSetup.md)
```
// .env 环境变量,开发测试 mongo、redis 地址
APPSMITH_MONGODB_URI="mongodb://10.10.13.50:27017/appsmith"
@ -36,6 +39,7 @@ bash ./scripts/start-dev-server.sh
```
## 💕 合并 GitHub 更新
```
// 外网代理
export https_proxy=http://127.0.0.1:3083
@ -50,6 +54,7 @@ git push origin master
```
## 📦 打包发布
```
// 前端打包
cd app/client
@ -77,6 +82,7 @@ docker-registry-idc01-sz.cloudtogo.cn/pageplug-server:cloudtogo
```
## 🌱 系统安装脚本
```
install.sh
@ -86,6 +92,7 @@ sed -i 's/index\.docker\.io\/appsmith\/appsmith-server/harbor\.cloud2go\.cn\/clo
```
## 🌼 小程序环境变量
```
// 小程序 ID、密钥用于获取小程序码
CLOUDOS_WECHAT_APPID="wx414ad0dbeda1a70b"
@ -93,6 +100,7 @@ CLOUDOS_WECHAT_SECRET="d5289fd08b1fb31290f66ea2ce5ec7dc"
```
## 🔔 StarOS 版本注意
```
// StarOS 版本环境变量
// 同步春景 API 列表
@ -104,7 +112,7 @@ CLOUDOS_IN_CLOUDOS=true
// nginx.conf 配置前端环境变量,替换 index.html 中的配置
sub_filter __PAGEPLUG_CLOUDOS_LOGIN_URL__ 'http://factory.dev.staros.local/user/login';
sub_filter __BMAP_AK__ 'nWCpSjRnXLfGuBc3iLZ9kYv8Y6wYaxf8';
🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨
StarOS 版本使用固定用户
在开启 inCloudOS 前需要

View File

@ -19,12 +19,14 @@
transition: all ease-in 0.3s;
}
</style>
<script type="text/javascript">
if ("%REACT_APP_IN_CLOUDOS%".trim() === "true") {
document.querySelector(':root').style.setProperty("--loader-bar-color", "#613eea");
document.title = "Methodot";
}
</script>
<script src="/logger.js"></script>
<script>
// '' (empty strings), 'false' are falsy
// could return either boolean or string based on value
@ -55,7 +57,20 @@
head && head.appendChild(script);
}
</script>
<script src="/logger.js"></script>
<script>
const BMAP_AK = parseConfig("__BMAP_AK__");
const script = document.createElement('script');
script.type = "text/javascript"
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${BMAP_AK}`;
const head = document.getElementsByTagName('head')[0];
head && head.appendChild(script);
script.onload = () => {
const script1 = document.createElement('script');
script1.src = `https://api.map.baidu.com/getscript?v=3.0&ak=${BMAP_AK}&services=&t=${(new Date()).getTime()}`
script1.type = "text/javascript"
head && head.appendChild(script1);
}
</script>
</head>
<body class="appsmith-light-theme">

View File

@ -114,6 +114,7 @@ export type UpdateApplicationPayload = {
appLayout?: AppLayoutConfig;
applicationVersion?: number;
viewerLayout?: string;
chartTheme?: string;
};
export type UpdateApplicationRequest = UpdateApplicationPayload & {
@ -129,6 +130,7 @@ export interface ApplicationObject {
workspaceId: string;
pages: ApplicationPagePayload[];
userPermissions: string[];
chartTheme?: string;
}
export interface PermissionGroup {

View File

@ -481,6 +481,8 @@ export const ReduxActionTypes = {
UPDATE_CANVAS_STRUCTURE: "UPDATE_CANVAS_STRUCTURE",
SET_SELECTED_WIDGET_ANCESTORY: "SET_SELECTED_WIDGET_ANCESTORY",
START_EVALUATION: "START_EVALUATION",
CURRENT_APPLICATION_CHART_THEME_UPDATE:
"CURRENT_APPLICATION_CHART_THEME_UPDATE",
CURRENT_APPLICATION_NAME_UPDATE: "CURRENT_APPLICATION_NAME_UPDATE",
CURRENT_APPLICATION_LAYOUT_UPDATE: "CURRENT_APPLICATION_LAYOUT_UPDATE",
CURRENT_APPLICATION_VIEWER_LAYOUT_UPDATE:
@ -1024,6 +1026,7 @@ export interface ApplicationPayload {
viewerLayout?: string;
isAutoUpdate?: boolean;
isManualUpdate?: boolean;
chartTheme?: string;
}
export type WorkspaceDetails = {

View File

@ -988,6 +988,10 @@ export const APP_THEME_BETA_CARD_HEADING = () => `🎨 修改应用风格`;
export const APP_THEME_BETA_CARD_CONTENT = () =>
`自定义全局样式风格,后面对所有组件进行支持`;
// echat theme
export const CHANGE_ECHART_THEME = (name: string) =>
`Echart主题已切换为 ${name} 风格主题`;
export const UPGRADE_TO_EE = (authLabel: string) =>
`你好,我想升级并且开始使用 ${authLabel} 鉴权`;
export const UPGRADE_TO_EE_GENERIC = () => `你好,我想升级`;

View File

@ -133,6 +133,16 @@ export const handlers = {
currentApplication: action.payload,
isFetchingApplication: false,
}),
[ReduxActionTypes.CURRENT_APPLICATION_CHART_THEME_UPDATE]: (
state: ApplicationsReduxState,
action: ReduxAction<{ chartTheme: string }>,
) => ({
...state,
currentApplication: {
...state.currentApplication,
chartTheme: action.payload.chartTheme,
},
}),
[ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE]: (
state: ApplicationsReduxState,
action: ReduxAction<{ name: string; slug: string }>,

View File

@ -86,6 +86,12 @@ export const WIDGET_DATA_FIELD_MAP: Record<string, WidgetBindingInfo> = {
widgetName: "Chart",
image: "https://assets.appsmith.com/widgetSuggestion/chart.svg",
},
ECHART_WIDGET: {
label: "chart-series-data-control",
propertyName: "chartData",
widgetName: "EChart",
image: "https://assets.appsmith.com/widgetSuggestion/chart.svg",
},
SELECT_WIDGET: {
label: "选项",
propertyName: "options",
@ -147,6 +153,20 @@ function getWidgetProps(
dynamicBindingPathList: [{ key: `chartData.${reactKey}.data` }],
},
};
case "ECHART_WIDGET":
const ereactKey = generateReactKey();
return {
type: suggestedWidget.type,
props: {
[fieldName]: {
[ereactKey]: {
seriesName: "Sales",
data: `{{${actionName}.${suggestedWidget.bindingQuery}}}`,
},
},
dynamicBindingPathList: [{ key: `echartData.${ereactKey}.data` }],
},
};
case "SELECT_WIDGET":
return {
type: suggestedWidget.type,

View File

@ -677,6 +677,7 @@ class CodeEditor extends Component<Props, State> {
});
}
const inputValue = this.props.input.value || "";
// console.log(inputValue, "handleChange");
if (
this.props.input.onChange &&
((value !== inputValue && this.state.isFocused) ||

View File

@ -0,0 +1,328 @@
import React from "react";
import { get, isString } from "lodash";
import BaseControl, { ControlProps } from "./BaseControl";
import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
import styled from "constants/DefaultTheme";
import { FormIcons } from "icons/FormIcons";
import { AnyStyledComponent } from "styled-components";
import { CodeEditorExpected } from "components/editorComponents/CodeEditor";
import {
EditorModes,
EditorSize,
EditorTheme,
TabBehaviour,
} from "components/editorComponents/CodeEditor/EditorConfig";
import { Size, Category } from "design-system";
import { AllChartData, ChartData } from "widgets/ChartWidget/constants";
import { generateReactKey } from "utils/generators";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
import CodeEditor from "components/editorComponents/LazyCodeEditorWrapper";
const Wrapper = styled.div`
background-color: ${(props) =>
props.theme.colors.propertyPane.dropdownSelectBg};
padding: 0 8px;
`;
const StyledOptionControlWrapper = styled(ControlWrapper)`
display: flex;
justify-content: flex-start;
padding: 0;
width: 100%;
`;
const StyledDynamicInput = styled.div`
width: 100%;
&&& {
input {
border: none;
color: ${(props) => props.theme.colors.textOnDarkBG};
background: ${(props) => props.theme.colors.paneInputBG};
&:focus {
border: none;
color: ${(props) => props.theme.colors.textOnDarkBG};
background: ${(props) => props.theme.colors.paneInputBG};
}
}
}
`;
const StyledDeleteIcon = styled(FormIcons.DELETE_ICON as AnyStyledComponent)`
padding: 0;
position: relative;
margin-left: 15px;
cursor: pointer;
&&& svg {
path {
fill: ${(props) => props.theme.colors.propertyPane.jsIconBg};
}
}
`;
const ActionHolder = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
`;
const StyledLabel = styled.label`
margin: 8px auto 8px 0;
&& {
color: ${(props) => props.theme.colors.propertyPane.label};
}
`;
const Box = styled.div`
height: 16px;
`;
type labelType = {
titleLabel: string;
areaLabel: string;
btnLabel: string;
};
type RenderComponentProps = {
index: string;
item: ChartData;
length: number;
dataTreePath: string;
deleteOption: (index: string) => void;
updateOption: (index: string, key: string, value: string) => void;
evaluated: {
seriesName: string;
data: Array<{ name: string; value: string }> | any;
};
theme: EditorTheme;
noTitle?: boolean;
label?: labelType;
};
const expectedSeriesName: CodeEditorExpected = {
type: "string",
example: "series1",
autocompleteDataType: AutocompleteDataType.STRING,
};
const expectedSeriesData: CodeEditorExpected = {
type: "Array<{ name: string, value: number Required }>",
example: [
{
name: "Mon",
value: 10000,
},
],
autocompleteDataType: AutocompleteDataType.ARRAY,
};
function DataControlComponent(props: RenderComponentProps) {
const {
dataTreePath,
deleteOption,
evaluated,
index,
item,
length,
updateOption,
noTitle,
label,
} = props;
return (
<StyledOptionControlWrapper orientation={"VERTICAL"}>
{!noTitle ? (
<>
<ActionHolder>
<StyledLabel>{label?.titleLabel}</StyledLabel>
{length > 1 && (
<StyledDeleteIcon
height={20}
onClick={() => {
deleteOption(index);
}}
width={20}
/>
)}
</ActionHolder>
<StyledOptionControlWrapper orientation={"HORIZONTAL"}>
<CodeEditor
dataTreePath={`${dataTreePath}.seriesName`}
evaluatedValue={evaluated?.seriesName}
expected={expectedSeriesName}
input={{
value: item.seriesName,
onChange: (
event: React.ChangeEvent<HTMLTextAreaElement> | string,
) => {
let value: string = event as string;
if (typeof event !== "string") {
value = event.target.value;
}
updateOption(index, "seriesName", value);
},
}}
mode={EditorModes.JAVASCRIPT}
placeholder={label?.titleLabel}
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INPUT}
theme={props.theme}
/>
</StyledOptionControlWrapper>
</>
) : null}
<StyledLabel>{label?.areaLabel}</StyledLabel>
<StyledDynamicInput
className={"t--property-control-chart-series-data-control"}
>
<CodeEditor
dataTreePath={`${dataTreePath}.data`}
evaluatedValue={evaluated?.data}
expected={expectedSeriesData}
input={{
value: item.data,
onChange: (
event: React.ChangeEvent<HTMLTextAreaElement> | string,
) => {
let value: string = event as string;
if (typeof event !== "string") {
value = event.target.value;
}
updateOption(index, "data", value);
},
}}
mode={EditorModes.JSON_WITH_BINDING}
placeholder={label?.areaLabel}
size={EditorSize.EXTENDED}
tabBehaviour={TabBehaviour.INPUT}
theme={props.theme}
/>
</StyledDynamicInput>
<Box />
</StyledOptionControlWrapper>
);
}
const Name_Data_Array = ["xAxis", "yAxis"];
const ControlLabel = (propertyName: string) => {
if (Name_Data_Array.includes(propertyName)) {
return {
titleLabel: "标签",
areaLabel: "数据",
hasAddBtn: false,
btnLabel: "ADD AXIS",
};
}
return {
titleLabel: "Series Title",
areaLabel: "Series Data",
hasAddBtn: true,
btnLabel: "ADD SERIES",
};
};
class EchartDataControl extends BaseControl<ControlProps> {
render() {
const chartData: AllChartData =
isString(this.props.propertyValue) || !this.props.propertyValue
? {}
: this.props.propertyValue;
const dataLength = Object.keys(chartData).length;
const isDataSet = this.props.propertyName === "dataSet";
const evaluatedValue = this.props.evaluatedValue;
const firstKey = Object.keys(chartData)[0] as string;
if (this.props.widgetProperties.chartType === "PIE_CHART") {
const data = dataLength
? get(chartData, `${firstKey}`)
: {
seriesName: "",
data: [],
};
return (
<DataControlComponent
noTitle={isDataSet}
dataTreePath={`${this.props.dataTreePath}.${firstKey}`}
deleteOption={this.deleteOption}
evaluated={get(evaluatedValue, `${firstKey}`)}
index={firstKey}
item={data}
length={1}
theme={this.props.theme}
updateOption={this.updateOption}
label={ControlLabel(this.props.propertyName)}
/>
);
}
return (
<>
<Wrapper>
{Object.keys(chartData).map((key: string) => {
const data = get(chartData, `${key}`);
return (
<DataControlComponent
noTitle={isDataSet}
dataTreePath={`${this.props.dataTreePath}.${key}`}
deleteOption={this.deleteOption}
evaluated={get(evaluatedValue, `${key}`)}
index={key}
item={data}
key={key}
length={dataLength}
theme={this.props.theme}
updateOption={this.updateOption}
label={ControlLabel(this.props.propertyName)}
/>
);
})}
</Wrapper>
{/* 新增按钮 */}
{ControlLabel(this.props.propertyName).hasAddBtn ? (
<StyledPropertyPaneButton
category={Category.tertiary}
icon="plus"
onClick={this.addOption}
size={Size.medium}
tag="button"
text={ControlLabel(this.props.propertyName).btnLabel}
type="button"
/>
) : null}
</>
);
}
deleteOption = (index: string) => {
this.deleteProperties([`${this.props.propertyName}.${index}`]);
};
updateOption = (
index: string,
propertyName: string,
updatedValue: string,
) => {
this.updateProperty(
`${this.props.propertyName}.${index}.${propertyName}`,
updatedValue,
);
};
/**
* it adds new series data object in the chartData
*/
addOption = () => {
const randomString = generateReactKey();
this.updateProperty(`${this.props.propertyName}.${randomString}`, {
seriesName: "",
data: JSON.stringify([{ name: "label", value: 51 }]),
});
};
static getControlType() {
return "ECHART_DATA";
}
}
export default EchartDataControl;

View File

@ -16,6 +16,7 @@ import DatePickerControl, {
DatePickerControlProps,
} from "components/propertyControls/DatePickerControl";
import ChartDataControl from "components/propertyControls/ChartDataControl";
import EchartDataControl from "components/propertyControls/EchartDataControl";
import LocationSearchControl from "components/propertyControls/LocationSearchControl";
import StepControl, {
StepControlProps,
@ -90,6 +91,7 @@ export const PropertyControls = {
ColumnActionSelectorControl,
MultiSwitchControl,
ChartDataControl,
EchartDataControl,
LocationSearchControl,
StepControl,
TabControl,

View File

@ -0,0 +1,64 @@
import { EchartTheme } from "entities/AppTheming";
import macarons from "theme/echart/macarons.json";
import chalk from "theme/echart/chalk.json";
import walden from "theme/echart/walden.json";
import westeros from "theme/echart/westeros.json";
type echartsMap = {
[key: string]: EchartTheme;
};
export const echartThemes: any = {
macarons: {
themeKey: "macarons",
...macarons,
},
chalk: {
themeKey: "chalk",
...chalk,
},
walden: {
themeKey: "walden",
...walden,
},
westeros: {
themeKey: "westeros",
...westeros,
},
dark: {
themeKey: "dark",
backgroundColor: "#100c2a",
color: [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
],
},
default: {
themeKey: "default",
backgroundColor: "#ffffff",
color: [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
],
},
transparent: {
themeKey: "transparent",
backgroundColor: "transparent",
color: [],
},
};

View File

@ -275,6 +275,10 @@ export const HelpMap: Record<string, { path: string; searchKey: string }> = {
path: "/widget-reference/map-chart",
searchKey: "Map Chart",
},
ECHART_WIDGET: {
path: "/widget-reference/chart",
searchKey: "Echart",
},
};
export const HelpBaseURL = "https://docs.appsmith.com";

View File

@ -70,3 +70,10 @@ export type AppTheme = {
};
};
};
export type EchartTheme = {
themeKey: string;
backgroundColor?: string;
color?: string[];
[key: string]: any;
};

View File

@ -21,6 +21,7 @@ export enum ActionTriggerType {
STOP_WATCHING_CURRENT_LOCATION = "STOP_WATCHING_CURRENT_LOCATION",
CONFIRMATION_MODAL = "CONFIRMATION_MODAL",
POST_MESSAGE = "POST_MESSAGE",
CALL_FUNC = "CALL_FUNC",
}
export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
@ -35,6 +36,7 @@ export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
[ActionTriggerType.SET_INTERVAL]: "setInterval",
[ActionTriggerType.SHOW_ALERT]: "showAlert",
[ActionTriggerType.SHOW_MODAL_BY_NAME]: "showModal",
[ActionTriggerType.CALL_FUNC]: "callFunc",
[ActionTriggerType.STORE_VALUE]: "storeValue",
[ActionTriggerType.REMOVE_VALUE]: "removeValue",
[ActionTriggerType.CLEAR_STORE]: "clearStore",
@ -193,12 +195,30 @@ export type PostMessageDescription = {
};
};
// type optionProps = {
// widgetName: string;
// funcName: string;
// params: any;
// };
type EchartActionPayload = {
options?: any;
widgetName: string;
funcName: string;
};
export type EchartAction = {
type: ActionTriggerType.CALL_FUNC;
payload: EchartActionPayload;
};
export type ActionDescription =
| RunPluginActionDescription
| ClearPluginActionDescription
| NavigateActionDescription
| ShowAlertActionDescription
| ShowModalActionDescription
| EchartAction
| CloseModalActionDescription
| StoreValueActionDescription
| RemoveValueActionDescription

View File

@ -15,6 +15,7 @@ const Title = styled.p`
type Props = {
skipThemeEditor?: boolean;
};
import EchartThemeEditor from "../ThemePropertyPane/Echart/EchartThemeEditor";
export function CanvasPropertyPane(props: Props) {
const isMobile = useSelector(isMobileLayout);
@ -31,6 +32,7 @@ export function CanvasPropertyPane(props: Props) {
)}
{!props.skipThemeEditor && <ThemeEditor />}
<EchartThemeEditor />
</div>
</div>
);

View File

@ -32,6 +32,7 @@ export const initialState: any = {
currentApplication: {
lastDeployedAt: null,
slug: "applicationSlug",
chartTheme: "default",
},
},
onBoarding: {

View File

@ -0,0 +1,88 @@
// import { createGlobalStyle } from "styled-components";
// import { get, startCase } from "lodash";
// import MoreIcon from "remixicon-react/MoreFillIcon";
import { useDispatch, useSelector } from "react-redux";
import React, { useCallback, useState } from "react";
import ThemeCard from "./ThemeCard";
import {
AppThemingMode,
getAppThemingStack,
getSelectedEchartTheme,
} from "selectors/appThemingSelectors";
import {
// resetThemeAction,
setAppThemingModeStackAction,
// updateSelectedAppThemeAction,
} from "actions/appThemingActions";
// import SettingSection from "../SettingSection";
import SaveThemeModal from "../SaveThemeModal";
// import { AppTheme } from "entities/AppTheming";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { Button, Category, Size } from "design-system";
// import { getCurrentApplicationId } from "selectors/editorSelectors";
import BetaCard from "components/editorComponents/BetaCard";
// const THEMING_BETA_CARD_POPOVER_CLASSNAME = `theming-beta-card-popover`;
function EchartThemeEditor() {
const dispatch = useDispatch();
// const applicationId = useSelector(getCurrentApplicationId);
// const selectedTheme = useSelector(getSelectedAppTheme);
const selectedEchartTheme = useSelector(getSelectedEchartTheme);
const themingStack = useSelector(getAppThemingStack);
const [isSaveModalOpen, setSaveModalOpen] = useState(false);
/**
* sets the mode to THEME_EDIT
*/
const onClickChangeThemeButton = useCallback(() => {
AnalyticsUtil.logEvent("APP_THEMING_CHOOSE_THEME");
dispatch(
setAppThemingModeStackAction([
...themingStack,
AppThemingMode.ECHART_THEME_SELECTION,
]),
);
}, [setAppThemingModeStackAction]);
/**
* on close save modal
*/
const onCloseSaveModal = useCallback(() => {
setSaveModalOpen(false);
}, [setSaveModalOpen]);
return (
<>
<div>
<header className="px-3 space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<h3 className="text-sm font-normal capitalize">Echart主题配置</h3>
<BetaCard />
</div>
</div>
<ThemeCard theme={selectedEchartTheme}>
<aside
className={`absolute left-0 top-0 bottom-0 right-0 items-center justify-center hidden group-hover:flex backdrop-filter bg-gray-900 bg-opacity-50 backdrop-blur-sm `}
>
<Button
category={Category.primary}
className="t--change-theme-btn"
onClick={onClickChangeThemeButton}
size={Size.medium}
text="选择Echart主题"
/>
</aside>
</ThemeCard>
</header>
</div>
<SaveThemeModal isOpen={isSaveModalOpen} onClose={onCloseSaveModal} />
</>
);
}
export default EchartThemeEditor;

View File

@ -0,0 +1,82 @@
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import {
// getAppThemes,
getAppThemingStack,
// getSelectedAppTheme,
getSelectedEchartTheme,
} from "selectors/appThemingSelectors";
import * as _ from "lodash";
import { echartThemes as ECHART_THEMES } from "./constants";
import { ThemeCard } from "./ThemeCard";
import { SettingSection } from "../SettingSection";
import ArrowLeft from "remixicon-react/ArrowLeftSLineIcon";
import { setAppThemingModeStackAction } from "actions/appThemingActions";
function EchartThemeSelector() {
const dispatch = useDispatch();
// const themes = useSelector(getAppThemes);
const themingStack = useSelector(getAppThemingStack);
const selectedEchartTheme = useSelector(getSelectedEchartTheme);
/**
* goes to previous screen in the pane
*/
const onClickBack = () => {
dispatch(setAppThemingModeStackAction(themingStack.slice(0, -1)));
};
/**
* stores user saved themes
*/
// const userSavedThemes = themes.filter(
// (theme) => theme.isSystemTheme === false,
// );
const userSavedThemes = {};
/**
* stores default system themes
*/
const systemThemes = ECHART_THEMES;
const currentTheme =
ECHART_THEMES[selectedEchartTheme?.themeKey] || selectedEchartTheme;
return (
<div className="relative">
<section className="sticky top-0 items-center justify-between bg-white z-1 ">
<button
className="inline-flex items-center px-3 py-2 space-x-1 text-gray-500 cursor-pointer t--theme-select-back-btn"
onClick={onClickBack}
type="button"
>
<ArrowLeft className="w-4 h-4 transition-all transform" />
<h3 className="text-xs font-medium uppercase"></h3>
</button>
<SettingSection
className="px-3 py-3 border-t border-b"
isDefaultOpen={false}
title="当前Echart主题"
>
<ThemeCard theme={currentTheme} />
</SettingSection>
</section>
{_.size(userSavedThemes) > 0 && (
<section className="relative px-3 py-3 space-y-3">
<h3 className="text-base font-medium capitalize">Echart主题</h3>
{_.map(userSavedThemes, (theme) => (
<ThemeCard selectable theme={theme} />
))}
</section>
)}
<section className="relative px-3 py-3 space-y-3">
<h3 className="text-base font-medium capitalize">Echart主题</h3>
{_.map(systemThemes, (theme: any) => (
<ThemeCard selectable theme={theme} />
))}
</section>
</div>
);
}
export default EchartThemeSelector;

View File

@ -0,0 +1,177 @@
import { last } from "lodash";
import classNames from "classnames";
import styled from "styled-components";
import * as _ from "lodash";
import * as Sentry from "@sentry/react";
import React, { useState } from "react";
import { message } from "antd";
import { useDispatch, useSelector } from "react-redux";
// import DeleteIcon from "remixicon-react/DeleteBinLineIcon";
import { updateApplication } from "actions/applicationActions";
import {
AppThemingMode,
getAppThemingStack,
// getAppThemes,
// getSelectedEchartTheme,
} from "selectors/appThemingSelectors";
import { AppTheme, EchartTheme } from "entities/AppTheming";
// import AnalyticsUtil from "utils/AnalyticsUtil";
// import DeleteThemeModal from "../DeleteThemeModal";
import { getComplementaryGrayscaleColor } from "widgets/WidgetUtils";
import { getCurrentApplicationId } from "selectors/editorSelectors";
/**
* ----------------------------------------------------------------------------
* TYPES
*-----------------------------------------------------------------------------
*/
type ThemeCard = React.PropsWithChildren<{
theme: EchartTheme;
isSelected?: boolean;
className?: string;
selectable?: boolean;
deletable?: boolean;
}>;
const MainContainer = styled.main<{ backgroundColor: string }>`
background: ${({ backgroundColor }) => backgroundColor};
`;
const HeaderContainer = styled.main<{ primaryColor: string }>`
background-color: ${({ primaryColor }) => primaryColor};
color: ${({ primaryColor }) => getComplementaryGrayscaleColor(primaryColor)};
`;
const MainText = styled.main<{ backgroundColor: string }>`
color: ${({ backgroundColor }) =>
getComplementaryGrayscaleColor(backgroundColor)};
`;
const MainTextTransparentBg = styled.main<{ backgroundColor: string }>`
background: #fff;
background-image: linear-gradient(
45deg,
#e7e7e7 25%,
transparent 0,
transparent 75%,
#e7e7e7 0
),
linear-gradient(
45deg,
#e7e7e7 25%,
transparent 0,
transparent 75%,
#e7e7e7 0
);
background-position: 0 0, 15px 15px;
background-size: 30px 30px;
`;
const ThemeColorCircle = styled.main<{ backgroundColor: string }>`
background-color: ${({ backgroundColor }) => backgroundColor};
`;
/**
* ----------------------------------------------------------------------------
* COMPONENT
*-----------------------------------------------------------------------------
*/
export function ThemeCard(props: ThemeCard) {
const { selectable, theme } = props;
const dispatch = useDispatch();
// const selectedEchart = useSelector(getSelectedEchartTheme);
const themingStack = useSelector(getAppThemingStack);
const themingMode = last(themingStack);
const applicationId = useSelector(getCurrentApplicationId);
const isThemeSelectionMode =
themingMode === AppThemingMode.ECHART_THEME_SELECTION;
// colors
const userDefinedColors = _.slice(theme.color, 0, 6);
const backgroundColor = theme.backgroundColor;
/**
* fires action for changing theme
*
* NOTE: since we are same card in theme edit and theme selection,
* we don't need to fire the action in theme edit mode on click on the card
*/
const changeSelectedTheme = () => {
const data = {
id: applicationId,
chartTheme: theme.themeKey,
};
if (isThemeSelectionMode && selectable) {
dispatch(updateApplication(applicationId, data));
message.success(`Echart主题已切换为 ${theme.themeKey} 风格主题`);
}
};
const TRANSPARENT_BG = ["transparent", "rgba(0,0,0,0)"];
const MainWrap: any =
backgroundColor === "transparent" ? MainTextTransparentBg : MainContainer;
return (
<div className="space-y-1 group">
{selectable && (
<div className="flex items-center justify-between">
<h3 className="text-sm text-gray-600 break-all">
{props.theme.displayName}
</h3>
</div>
)}
<div
className={classNames({
"border relative group transition-all t--theme-card": true,
"overflow-hidden": !selectable,
"hover:shadow-xl cursor-pointer": selectable,
})}
onClick={changeSelectedTheme}
>
<MainWrap backgroundColor={backgroundColor}>
<section className="flex justify-between px-3 pt-3">
<MainText
backgroundColor={
TRANSPARENT_BG.includes(backgroundColor || "")
? "white"
: backgroundColor
}
className="text-base"
>
{theme.themeKey}
</MainText>
<div className="flex items-center space-x-2">
{userDefinedColors.map((colorKey: any, index: number) => (
<ThemeColorCircle
backgroundColor={colorKey}
className="w-4 h-4 border rounded-full"
key={index}
/>
))}
</div>
</section>
<section className="p-2">
<div className="flex space-x-2" />
</section>
</MainWrap>
<aside
className={`absolute bottom-0 left-0 right-0 items-center justify-center hidden bg-gray-900/80 ${
selectable ? "group-hover:flex" : ""
}`}
>
<div className="py-1 text-xs tracking-wide text-white uppercase">
使
</div>
</aside>
{props.children}
</div>
</div>
);
}
ThemeCard.displayName = "ThemeCard";
export default Sentry.withProfiler(ThemeCard);

View File

@ -0,0 +1,64 @@
import { EchartTheme } from "entities/AppTheming";
import macarons from "theme/echart/macarons.json";
import chalk from "theme/echart/chalk.json";
import walden from "theme/echart/walden.json";
import westeros from "theme/echart/westeros.json";
type echartsMap = {
[key: string]: EchartTheme;
};
export const echartThemes: any = {
macarons: {
themeKey: "macarons",
...macarons,
},
chalk: {
themeKey: "chalk",
...chalk,
},
walden: {
themeKey: "walden",
...walden,
},
westeros: {
themeKey: "westeros",
...westeros,
},
dark: {
themeKey: "dark",
backgroundColor: "#100c2a",
color: [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
],
},
default: {
themeKey: "default",
backgroundColor: "#ffffff",
color: [
"#5470c6",
"#91cc75",
"#fac858",
"#ee6666",
"#73c0de",
"#3ba272",
"#fc8452",
"#9a60b4",
"#ea7ccc",
],
},
transparent: {
themeKey: "transparent",
backgroundColor: "transparent",
color: [],
},
};

View File

@ -4,6 +4,7 @@ import { last } from "lodash";
import ThemeEditor from "./ThemeEditor";
import ThemeSelector from "./ThemeSelector";
import EchartThemeSelector from "./Echart/EchartThemeSelector";
import {
AppThemingMode,
getAppThemingStack,
@ -26,6 +27,8 @@ export function ThemePropertyPane() {
return <ThemeEditor />;
case themingMode === AppThemingMode.APP_THEME_SELECTION:
return <ThemeSelector />;
case themingMode === AppThemingMode.ECHART_THEME_SELECTION:
return <EchartThemeSelector />;
default:
return <ThemeEditor />;
}

View File

@ -40,6 +40,7 @@ export type AppDataState = {
canBeRequested: boolean;
currentPosition?: Partial<GeolocationPosition>;
};
echartInstance: any;
};
const initialState: AppDataState = {
@ -67,6 +68,7 @@ const initialState: AppDataState = {
canBeRequested: "geolocation" in navigator,
currentPosition: {},
},
echartInstance: {},
};
const appReducer = createReducer(initialState, {

View File

@ -46,6 +46,7 @@ import {
setIntervalSaga,
} from "sagas/ActionExecution/SetIntervalSaga";
import {
getEchartSaga,
getCurrentLocationSaga,
stopWatchCurrentLocation,
watchCurrentLocation,
@ -128,7 +129,14 @@ export function* executeActionTriggers(
triggerMeta,
);
break;
case ActionTriggerType.CALL_FUNC:
response = yield call(
getEchartSaga,
trigger.payload,
eventType,
triggerMeta,
);
break;
case ActionTriggerType.WATCH_CURRENT_LOCATION:
response = yield call(
watchCurrentLocation,

View File

@ -1,5 +1,6 @@
import {
GetCurrentLocationDescription,
EchartAction,
WatchCurrentLocationDescription,
} from "entities/DataTree/actionTriggers";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
@ -7,13 +8,19 @@ import {
executeAppAction,
TriggerMeta,
} from "sagas/ActionExecution/ActionExecutionSagas";
import { call, put, spawn, take } from "redux-saga/effects";
import _find from "lodash/find";
import { call, put, spawn, take, select } from "redux-saga/effects";
import {
logActionExecutionError,
TriggerFailureError,
} from "sagas/ActionExecution/errorUtils";
import { setUserCurrentGeoLocation } from "actions/browserRequestActions";
import { Channel, channel } from "redux-saga";
import { getEchartWidget } from "selectors/widgetSelectors";
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
import { ActionValidationError } from "sagas/ActionExecution/errorUtils";
import { ActionTriggerType } from "entities/DataTree/actionTriggers";
import { getType, Types } from "utils/TypeHelpers";
// Making the getCurrentPosition call in a promise fashion
const getUserLocation = (options?: PositionOptions) =>
@ -128,6 +135,56 @@ export function* getCurrentLocationSaga(
}
}
export function* getEchartSaga(
actionPayload: EchartAction["payload"],
eventType: EventType,
triggerMeta: TriggerMeta,
) {
const { widgetName, funcName, options } = actionPayload;
const echartWidgets: {
[widgetId: string]: FlattenedWidgetProps;
} = yield select(getEchartWidget);
const _target = _find(echartWidgets, (it: any) => it.name === widgetName);
const _instance = _target?.instance;
if (typeof widgetName !== "string") {
throw new ActionValidationError(
ActionTriggerType.CALL_FUNC,
"widgetName",
Types.STRING,
getType(widgetName),
);
}
if (typeof funcName !== "string") {
throw new ActionValidationError(
ActionTriggerType.CALL_FUNC,
"funcName",
Types.STRING,
getType(funcName),
);
}
try {
if (_instance) {
const res = _instance[funcName](options);
if (funcName === "getDom") {
logActionExecutionError(
"postmessage could not be cloned",
triggerMeta.source,
triggerMeta.triggerPropertyName,
);
}
return [res];
}
} catch (error) {
logActionExecutionError(
(error as Error).message,
triggerMeta.source,
triggerMeta.triggerPropertyName,
);
return [error];
}
}
let watchId: number | undefined;
export function* watchCurrentLocation(
actionPayload: WatchCurrentLocationDescription["payload"],

View File

@ -360,6 +360,10 @@ export function* updateApplicationSaga(
type: ReduxActionTypes.UPDATE_APPLICATION_SUCCESS,
payload: response.data,
});
yield put({
type: ReduxActionTypes.CURRENT_APPLICATION_CHART_THEME_UPDATE,
payload: response.data,
});
}
if (request.currentApp) {
yield put({

View File

@ -1,8 +1,10 @@
import { AppState } from "@appsmith/reducers";
import { echartThemes } from "constants/EchartContants";
export enum AppThemingMode {
APP_THEME_EDIT = "APP_THEME_EDIT",
APP_THEME_SELECTION = "APP_THEME_SELECTION",
ECHART_THEME_SELECTION = "ECHART_THEME_SELECTION",
}
/**
@ -45,6 +47,19 @@ export const getSelectedAppThemeStylesheet = (state: AppState) => {
return state.ui.appTheming.selectedTheme.stylesheet;
};
/**
* get the selected echart theme
*
* @param state
* @returns
*/
export const getSelectedEchartTheme = (state: AppState) => {
if (state.ui.applications.currentApplication?.chartTheme) {
return echartThemes[state.ui.applications.currentApplication?.chartTheme];
}
return echartThemes["default"];
};
/**
* get the preview theme or selected theme
*

View File

@ -1,5 +1,6 @@
import { createSelector } from "reselect";
import { AppState } from "@appsmith/reducers";
import * as echarts from "echarts";
import {
CanvasWidgetsReduxState,
FlattenedWidgetProps,
@ -43,6 +44,24 @@ export const getModalDropdownList = createSelector(
},
);
export const getEchartWidget = createSelector(getCanvasWidgets, (widgets) => {
const echartWidgets = Object.values(widgets).filter(
(widget: FlattenedWidgetProps) => widget.type === "ECHART_WIDGET",
);
if (echartWidgets.length === 0) return undefined;
return echartWidgets.map((widget: FlattenedWidgetProps) => {
const chartId = `${widget.widgetId}-echart-container`;
const c_dom: HTMLElement | null = document
? document.getElementById(`${chartId}`)
: null;
return {
id: widget.widgetId,
name: widget.widgetName,
instance: c_dom ? echarts.getInstanceByDom(c_dom) : null,
};
});
});
export const getNextModalName = createSelector(
getExistingWidgetNames,
(names) => {

View File

@ -0,0 +1,396 @@
{
"color": [
"#fc97af",
"#87f7cf",
"#f7f494",
"#72ccff",
"#f7c5a0",
"#d4a4eb",
"#d2f5a6",
"#76f2f2"
],
"backgroundColor": "rgba(41,52,65,1)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#ffffff"
},
"subtextStyle": {
"color": "#dddddd"
}
},
"line": {
"itemStyle": {
"borderWidth": "4"
},
"lineStyle": {
"width": "3"
},
"symbolSize": "0",
"symbol": "circle",
"smooth": true
},
"radar": {
"itemStyle": {
"borderWidth": "4"
},
"lineStyle": {
"width": "3"
},
"symbolSize": "0",
"symbol": "circle",
"smooth": true
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#fc97af",
"color0": "transparent",
"borderColor": "#fc97af",
"borderColor0": "#87f7cf",
"borderWidth": "2"
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"lineStyle": {
"width": "1",
"color": "#ffffff"
},
"symbolSize": "0",
"symbol": "circle",
"smooth": true,
"color": [
"#fc97af",
"#87f7cf",
"#f7f494",
"#72ccff",
"#f7c5a0",
"#d4a4eb",
"#d2f5a6",
"#76f2f2"
],
"label": {
"color": "#293441"
}
},
"map": {
"itemStyle": {
"areaColor": "#f3f3f3",
"borderColor": "#999999",
"borderWidth": 0.5
},
"label": {
"color": "#893448"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(255,178,72,1)",
"borderColor": "#eb8146",
"borderWidth": 1
},
"label": {
"color": "rgb(137,52,72)"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#f3f3f3",
"borderColor": "#999999",
"borderWidth": 0.5
},
"label": {
"color": "#893448"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(255,178,72,1)",
"borderColor": "#eb8146",
"borderWidth": 1
},
"label": {
"color": "rgb(137,52,72)"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#666666"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#aaaaaa"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#e6e6e6"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#666666"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#aaaaaa"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#e6e6e6"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#666666"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#aaaaaa"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#e6e6e6"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#666666"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#aaaaaa"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#e6e6e6"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#999999"
},
"emphasis": {
"iconStyle": {
"borderColor": "#666666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#cccccc",
"width": 1
},
"crossStyle": {
"color": "#cccccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#87f7cf",
"width": 1
},
"itemStyle": {
"color": "#87f7cf",
"borderWidth": 1
},
"controlStyle": {
"color": "#87f7cf",
"borderColor": "#87f7cf",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#fc97af",
"borderColor": "#fc97af"
},
"label": {
"color": "#87f7cf"
},
"emphasis": {
"itemStyle": {
"color": "#f7f494"
},
"controlStyle": {
"color": "#87f7cf",
"borderColor": "#87f7cf",
"borderWidth": 0.5
},
"label": {
"color": "#87f7cf"
}
}
},
"visualMap": {
"color": [
"#fc97af",
"#87f7cf"
]
},
"dataZoom": {
"backgroundColor": "rgba(255,255,255,0)",
"dataBackgroundColor": "rgba(114,204,255,1)",
"fillerColor": "rgba(114,204,255,0.2)",
"handleColor": "#72ccff",
"handleSize": "100%",
"textStyle": {
"color": "#333333"
}
},
"markPoint": {
"label": {
"color": "#293441"
},
"emphasis": {
"label": {
"color": "#293441"
}
}
}
}

View File

@ -0,0 +1,420 @@
{
"color": [
"#2ec7c9",
"#b6a2de",
"#5ab1ef",
"#ffb980",
"#d87a80",
"#8d98b3",
"#e5cf0d",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#c05050",
"#59678c",
"#c9ab00",
"#7eb00a",
"#6f5553",
"#c14089"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#008acd"
},
"subtextStyle": {
"color": "#aaaaaa"
}
},
"line": {
"itemStyle": {
"borderWidth": 1
},
"lineStyle": {
"width": 2
},
"symbolSize": 3,
"symbol": "emptyCircle",
"smooth": true
},
"radar": {
"itemStyle": {
"borderWidth": 1
},
"lineStyle": {
"width": 2
},
"symbolSize": 3,
"symbol": "emptyCircle",
"smooth": true
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#d87a80",
"color0": "#2ec7c9",
"borderColor": "#d87a80",
"borderColor0": "#2ec7c9",
"borderWidth": 1
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"lineStyle": {
"width": 1,
"color": "#aaaaaa"
},
"symbolSize": 3,
"symbol": "emptyCircle",
"smooth": true,
"color": [
"#2ec7c9",
"#b6a2de",
"#5ab1ef",
"#ffb980",
"#d87a80",
"#8d98b3",
"#e5cf0d",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#c05050",
"#59678c",
"#c9ab00",
"#7eb00a",
"#6f5553",
"#c14089"
],
"label": {
"color": "#eeeeee"
}
},
"map": {
"itemStyle": {
"areaColor": "#dddddd",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"label": {
"color": "#d87a80"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(254,153,78,1)",
"borderColor": "#444",
"borderWidth": 1
},
"label": {
"color": "rgb(100,0,0)"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#dddddd",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"label": {
"color": "#d87a80"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(254,153,78,1)",
"borderColor": "#444",
"borderWidth": 1
},
"label": {
"color": "rgb(100,0,0)"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#008acd"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#008acd"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": true,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#008acd"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": true,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#008acd"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#2ec7c9"
},
"emphasis": {
"iconStyle": {
"borderColor": "#18a4a6"
}
}
},
"legend": {
"textStyle": {
"color": "#333333"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#008acd",
"width": "1"
},
"crossStyle": {
"color": "#008acd",
"width": "1"
}
}
},
"timeline": {
"lineStyle": {
"color": "#008acd",
"width": 1
},
"itemStyle": {
"color": "#008acd",
"borderWidth": 1
},
"controlStyle": {
"color": "#008acd",
"borderColor": "#008acd",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#2ec7c9",
"borderColor": "#2ec7c9"
},
"label": {
"color": "#008acd"
},
"emphasis": {
"itemStyle": {
"color": "#a9334c"
},
"controlStyle": {
"color": "#008acd",
"borderColor": "#008acd",
"borderWidth": 0.5
},
"label": {
"color": "#008acd"
}
}
},
"visualMap": {
"color": [
"#5ab1ef",
"#e0ffff"
]
},
"dataZoom": {
"backgroundColor": "rgba(47,69,84,0)",
"dataBackgroundColor": "#efefff",
"fillerColor": "rgba(182,162,222,0.2)",
"handleColor": "#008acd",
"handleSize": "100%",
"textStyle": {
"color": "#333333"
}
},
"markPoint": {
"label": {
"color": "#eeeeee"
},
"emphasis": {
"label": {
"color": "#eeeeee"
}
}
}
}

View File

@ -0,0 +1,392 @@
{
"color": [
"#3fb1e3",
"#6be6c1",
"#626c91",
"#a0a7e6",
"#c4ebad",
"#96dee8"
],
"backgroundColor": "rgba(252,252,252,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#666666"
},
"subtextStyle": {
"color": "#999999"
}
},
"line": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "3"
},
"symbolSize": "8",
"symbol": "emptyCircle",
"smooth": false
},
"radar": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "3"
},
"symbolSize": "8",
"symbol": "emptyCircle",
"smooth": false
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#e6a0d2",
"color0": "transparent",
"borderColor": "#e6a0d2",
"borderColor0": "#3fb1e3",
"borderWidth": "2"
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"lineStyle": {
"width": "1",
"color": "#cccccc"
},
"symbolSize": "8",
"symbol": "emptyCircle",
"smooth": false,
"color": [
"#3fb1e3",
"#6be6c1",
"#626c91",
"#a0a7e6",
"#c4ebad",
"#96dee8"
],
"label": {
"color": "#ffffff"
}
},
"map": {
"itemStyle": {
"areaColor": "#eeeeee",
"borderColor": "#aaaaaa",
"borderWidth": 0.5
},
"label": {
"color": "#ffffff"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(63,177,227,0.25)",
"borderColor": "#3fb1e3",
"borderWidth": 1
},
"label": {
"color": "#3fb1e3"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#eeeeee",
"borderColor": "#aaaaaa",
"borderWidth": 0.5
},
"label": {
"color": "#ffffff"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(63,177,227,0.25)",
"borderColor": "#3fb1e3",
"borderWidth": 1
},
"label": {
"color": "#3fb1e3"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#999999"
},
"emphasis": {
"iconStyle": {
"borderColor": "#666666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#cccccc",
"width": 1
},
"crossStyle": {
"color": "#cccccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#626c91",
"width": 1
},
"itemStyle": {
"color": "#626c91",
"borderWidth": 1
},
"controlStyle": {
"color": "#626c91",
"borderColor": "#626c91",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#3fb1e3",
"borderColor": "#3fb1e3"
},
"label": {
"color": "#626c91"
},
"emphasis": {
"itemStyle": {
"color": "#626c91"
},
"controlStyle": {
"color": "#626c91",
"borderColor": "#626c91",
"borderWidth": 0.5
},
"label": {
"color": "#626c91"
}
}
},
"visualMap": {
"color": [
"#2a99c9",
"#afe8ff"
]
},
"dataZoom": {
"backgroundColor": "rgba(255,255,255,0)",
"dataBackgroundColor": "rgba(222,222,222,1)",
"fillerColor": "rgba(114,230,212,0.25)",
"handleColor": "#cccccc",
"handleSize": "100%",
"textStyle": {
"color": "#999999"
}
},
"markPoint": {
"label": {
"color": "#ffffff"
},
"emphasis": {
"label": {
"color": "#ffffff"
}
}
}
}

View File

@ -0,0 +1,393 @@
{
"color": [
"#516b91",
"#59c4e6",
"#edafda",
"#93b7e3",
"#a5e7f0",
"#cbb0e3"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#516b91"
},
"subtextStyle": {
"color": "#93b7e3"
}
},
"line": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"radar": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"bar": {
"itemStyle": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
},
"pie": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"scatter": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"parallel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"sankey": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"funnel": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"gauge": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"candlestick": {
"itemStyle": {
"color": "#edafda",
"color0": "transparent",
"borderColor": "#d680bc",
"borderColor0": "#8fd3e8",
"borderWidth": "2"
}
},
"graph": {
"itemStyle": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"lineStyle": {
"width": 1,
"color": "#aaaaaa"
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true,
"color": [
"#516b91",
"#59c4e6",
"#edafda",
"#93b7e3",
"#a5e7f0",
"#cbb0e3"
],
"label": {
"color": "#eeeeee"
}
},
"map": {
"itemStyle": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"label": {
"color": "#000"
},
"emphasis": {
"itemStyle": {
"areaColor": "#a5e7f0",
"borderColor": "#516b91",
"borderWidth": 1
},
"label": {
"color": "#516b91"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"label": {
"color": "#000"
},
"emphasis": {
"itemStyle": {
"areaColor": "#a5e7f0",
"borderColor": "#516b91",
"borderWidth": 1
},
"label": {
"color": "#516b91"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"color": "#999999"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#999999"
},
"emphasis": {
"iconStyle": {
"borderColor": "#666666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#cccccc",
"width": 1
},
"crossStyle": {
"color": "#cccccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#8fd3e8",
"width": 1
},
"itemStyle": {
"color": "#8fd3e8",
"borderWidth": 1
},
"controlStyle": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#8fd3e8",
"borderColor": "#8a7ca8"
},
"label": {
"color": "#8fd3e8"
},
"emphasis": {
"itemStyle": {
"color": "#8fd3e8"
},
"controlStyle": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
},
"label": {
"color": "#8fd3e8"
}
}
},
"visualMap": {
"color": [
"#516b91",
"#59c4e6",
"#a5e7f0"
]
},
"dataZoom": {
"backgroundColor": "rgba(0,0,0,0)",
"dataBackgroundColor": "rgba(255,255,255,0.3)",
"fillerColor": "rgba(167,183,204,0.4)",
"handleColor": "#a7b7cc",
"handleSize": "100%",
"textStyle": {
"color": "#333"
}
},
"markPoint": {
"label": {
"color": "#eeeeee"
},
"emphasis": {
"label": {
"color": "#eeeeee"
}
}
}
}

View File

@ -26,3 +26,21 @@ const getEnvLogLevel = (configLevel: LogLevelDesc): LogLevelDesc => {
}
return logLevel;
};
/**
* read json data from json url
* @param blobUrl
* @returns
*/
export async function readBlob(blobUrl: string): Promise<any> {
const file = await fetch(blobUrl, {
referrer: "",
}).then((r) => r.blob());
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsText(file);
reader.onloadend = () => {
resolve(reader.result);
};
});
}

View File

@ -2,6 +2,7 @@ import _, { get, isString, VERSION as lodashVersion } from "lodash";
import { DATA_BIND_REGEX } from "constants/BindingsConstants";
import { Action } from "entities/Action";
import moment from "moment-timezone";
import * as echarts from "echarts";
import { WidgetProps } from "widgets/BaseWidget";
import parser from "fast-xml-parser";
@ -187,6 +188,13 @@ export const extraLibraries: ExtraLibrary[] = [
docsURL: "https://github.com/digitalbazaar/forge",
displayName: "forge",
},
{
accessor: "echarts",
lib: echarts,
version: "5.4.0",
docsURL: `https://echarts.apache.org/handbook/zh/how-to/animation/transition/`,
displayName: "echarts",
},
];
/**
* creates dynamic list of constants based on

View File

@ -23,6 +23,9 @@ import CanvasWidget, {
import ChartWidget, {
CONFIG as CHART_WIDGET_CONFIG,
} from "widgets/ChartWidget";
import EChartWidget, {
CONFIG as ECHART_WIDGET_CONFIG,
} from "widgets/EchartWidget";
import CheckboxGroupWidget, {
CONFIG as CHECKBOX_GROUP_WIDGET_CONFIG,
} from "widgets/CheckboxGroupWidget";
@ -240,6 +243,7 @@ export const ALL_WIDGETS_AND_CONFIG = [
[TabsWidget, TABS_WIDGET_CONFIG],
[ModalWidget, MODAL_WIDGET_CONFIG],
[ChartWidget, CHART_WIDGET_CONFIG],
[EChartWidget, ECHART_WIDGET_CONFIG],
[MapWidget, MAP_WIDGET_CONFIG],
[RichTextEditorWidget, RICH_TEXT_EDITOR_WIDGET_CONFIG],
[DatePickerWidget2, DATE_PICKER_WIDGET_2_CONFIG],

View File

@ -39,6 +39,10 @@ export const entityDefinitions = {
watchPosition: "fn(options: object) -> void",
clearWatch: "fn() -> void",
},
echartInstance: {
callFunc:
"fn(widgetName: string, funcName: string, options?: any) -> +Promise[:t=[!0.<i>.:t]]",
},
};
}
return generatedTypeDef;
@ -350,6 +354,20 @@ export const entityDefinitions = {
yAxisName: "string",
selectedDataPoint: "$__chartDataPoint__$",
},
ECHART_WIDGET: {
"!doc":
"EChart widget is used to view the graphical representation of your data. Echart is the go-to widget for your data visualisation needs.",
"!url": "https://docs.appsmith.com/widget-reference/chart",
isVisible: true,
chartData: {
seriesName: "string",
// data: "[$__chartDataPoint__$]",
data: "any",
},
xAxisName: "string",
yAxisName: "string",
selectedDataItem: "any",
},
FORM_WIDGET: (widget: any, extraDefsToDefine?: ExtraDef) => ({
"!doc":
"Form is used to capture a set of data inputs from a user. Forms are used specifically because they reset the data inputs when a form is submitted and disable submission for invalid data inputs",

View File

@ -3,7 +3,7 @@ import { DSLWidget } from "widgets/constants";
export const migrateChartWidgetReskinningData = (currentDSL: DSLWidget) => {
currentDSL.children = currentDSL.children?.map((child: WidgetProps) => {
if (child.type === "CHART_WIDGET") {
if (child.type === "CHART_WIDGET" || child.type === "ECHART_WIDGET") {
if (
!(
child.hasOwnProperty("accentColor") &&

View File

@ -0,0 +1,303 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { useEffect, useRef, useState } from "react";
import * as echarts from "echarts";
import "echarts-gl";
import * as _ from "lodash";
import "echarts/extension/bmap/bmap";
import macarons from "theme/echart/macarons.json";
import chalk from "theme/echart/chalk.json";
import walden from "theme/echart/walden.json";
import westeros from "theme/echart/westeros.json";
import { useSelector } from "react-redux";
import { EchartTheme } from "entities/AppTheming";
import { getSelectedEchartTheme } from "selectors/appThemingSelectors";
import { LabelOrientation, AllChartData } from "../constants";
import { NO_AXIS, ECHART_BASIC_OPTION, ECHART_TYPE_MAP } from "../constants";
import { convertStringFunciton } from "../widget/helper";
import { readBlob } from "utils/AppUtils";
import { message } from "antd";
interface pointType {
name: string | number;
value: number;
}
function customizer(objValue: any, srcValue: any) {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
}
const DARK_THEMES = ["dark", "chalk", "transparent"];
function EchartComponent(props: EchartComponentProps) {
const echartRef = useRef<any>();
const echartInstance = useRef<any>();
const selectedEchartTheme = useSelector(getSelectedEchartTheme);
const objectRef = useRef<any>();
const [preChartTheme, setCurrentChart] = useState<EchartTheme | null>();
const chartContainerId = props.widgetId + "-echart-container";
const {
onDataPointClick,
chartType,
chartName,
xAxisName,
yAxisName,
chartData,
customEchartConfig,
borderRadius,
boxShadow,
backgroundColor,
registerMapJsonUrl,
registerMapName,
} = props;
const handleEvent = () => {
const _echartInstance = echartInstance.current;
const { onDataPointClick } = props;
if (onDataPointClick && _echartInstance) {
_echartInstance.on("click", (evt: any) => {
onDataPointClick(evt);
});
}
};
const registerEchartTheme = (theme: string | undefined) => {
switch (theme) {
case "macarons":
echarts.registerTheme("macarons", macarons);
break;
case "chalk":
echarts.registerTheme("chalk", chalk);
break;
case "westeros":
echarts.registerTheme("westeros", westeros);
break;
case "walden":
echarts.registerTheme("walden", walden);
break;
default:
break;
}
};
const registerMap = () => {
if (registerMapName && registerMapJsonUrl && echartInstance.current) {
if (echarts.getMap(registerMapName)) {
// already register
} else {
readBlob(registerMapJsonUrl)
.then((resData) => {
echarts.registerMap(registerMapName, resData);
})
.catch(() => {
message.warn("地图注册失败");
});
}
}
};
useEffect(() => {
const { onInstance } = props;
onInstance && echartInstance.current && onInstance(echartInstance.current);
}, [echartInstance.current]);
// props更新调用
useEffect(() => {
if (!chartData) {
return;
}
if (!echartInstance.current && echartRef.current) {
echartInstance.current = echarts.init(echartRef?.current, "default");
}
/*register theme*/
const registerTheme: EchartTheme | undefined = selectedEchartTheme;
if (preChartTheme !== registerTheme && registerTheme) {
echartInstance.current.dispose();
registerEchartTheme(registerTheme.themeKey);
echartInstance.current = echarts.init(
echartRef?.current,
registerTheme.themeKey,
);
}
if (echartInstance.current) {
// 清空一下实例
echartInstance.current.clear();
const _seriesD = getSeriesAndXaxisData();
let newOption: any = {
title: {
text: chartName,
},
xAxis: {
name: xAxisName,
data: _seriesD.xAxis,
},
yAxis: {
name: yAxisName,
},
darkMode: true,
series: _seriesD.series,
};
if (backgroundColor) {
_.set(newOption, "backgroundColor", backgroundColor);
}
if (
selectedEchartTheme &&
DARK_THEMES.includes(selectedEchartTheme.themeKey)
) {
_.set(newOption, "darkMode", true);
}
if (chartType === "CUSTOM_CHART" && customEchartConfig) {
newOption = customEchartConfig;
}
_.mergeWith(newOption, ECHART_BASIC_OPTION, customizer);
/**
* opts.replaceMerge里指定组件类型
*
*/
const replaceMerge: string[] = [];
if (NO_AXIS[chartType]) {
replaceMerge.push("xAxis", "yAxis");
_.set(newOption, "yAxis.show", false);
_.set(newOption, "xAxis.show", false);
} else {
_.set(newOption, "yAxis.show", true);
_.set(newOption, "xAxis.show", true);
}
const newOptions: any = convertStringFunciton(newOption);
echartInstance.current.setOption(newOptions, { replaceMerge });
setCurrentChart(selectedEchartTheme);
}
}, [
chartType,
chartName,
xAxisName,
yAxisName,
chartData,
customEchartConfig,
backgroundColor,
selectedEchartTheme,
registerMapName,
registerMapJsonUrl,
]);
useEffect(() => {
registerMap();
}, [registerMapJsonUrl, registerMapName]);
// 点击事件handler
useEffect(() => {
return () => {
echartInstance.current &&
echartInstance.current.off("click", onDataPointClick);
};
}, [onDataPointClick]);
// bind event
useEffect(() => {
if (echartRef.current) {
handleEvent();
}
registerMap();
}, []);
const getSeriesAndXaxisData = () => {
const series: any[] = [];
const xAxis: any[] = [];
_.each(chartData, (c_data, c_key) => {
if (chartType === "PIE_CHART") {
series.push({
...c_data,
id: c_key,
name: c_data.seriesName,
type: ECHART_TYPE_MAP[chartType],
data: c_data.data.map((it: pointType) => {
return {
name: it.name,
value: it.value,
};
}),
});
} else {
series.push({
...c_data,
id: c_key,
name: c_data.seriesName,
type: ECHART_TYPE_MAP[chartType],
data: c_data.data.map((it: pointType) => {
!xAxis.includes(it.name) && xAxis.push(it.name);
return it.value;
}),
});
}
});
return { xAxis, series };
};
useEffect(() => {
objectRef.current.contentDocument.defaultView.addEventListener(
"resize",
() => {
echartInstance.current.resize();
},
);
}, [objectRef.current]);
const style = {
borderRadius,
boxShadow,
};
return (
<div
className={`w-full h-full overflow-hidden relative ${
_.includes(DARK_THEMES, selectedEchartTheme.themeKey)
? "bg-transparent"
: "bg-white"
}`}
style={style}
>
<div id={chartContainerId} ref={echartRef} className="h-full w-full" />
{/* 监听组件大小 */}
<object
ref={objectRef}
tabIndex={-1}
type="text/html"
aria-hidden="true"
data="about:blank"
className="absolute block top-0 left-0 w-full h-full border-0 opacity-0 z-[-1000] pointer-events-none"
/>
</div>
);
}
export interface EchartComponentProps {
widgetId?: string;
allowScroll: boolean;
chartData: AllChartData;
chartName: string;
chartType: string;
customEchartConfig: any;
isVisible?: boolean;
isLoading: boolean;
setAdaptiveYMin: boolean; // set adaptive YAxis
labelOrientation?: LabelOrientation; // adjust xAxis label orientation
onDataPointClick: (selectedDataPoint: any) => void;
onInstance: (instance?: any) => void;
xAxisName: string;
yAxisName: string;
borderRadius: string;
boxShadow?: string;
primaryColor?: string;
backgroundColor?: string;
fontFamily?: string;
registerMapJsonUrl?: string;
registerMapName?: string;
}
export default EchartComponent;

View File

@ -0,0 +1,142 @@
export type ChartType =
| "LINE_CHART"
| "BAR_CHART"
| "PIE_CHART"
| "COLUMN_CHART"
| "AREA_CHART"
| "SCATTER_CHART"
| "CUSTOM_CHART";
export interface ChartDataPoint {
x: any;
y: any;
}
export interface DataSetT {
value: any;
name: string;
}
export interface ChartData {
name?: string;
seriesName?: string;
data: DataSetT[];
type: string;
}
export interface CustomFusionChartConfig {
type: string;
dataSource?: any;
}
export interface AllChartData {
[key: string]: ChartData;
}
export interface ChartSelectedDataPoint {
x: any;
y: any;
seriesTitle: string;
}
export const defaultColors = [
"#3366FF",
"#91cc75",
"#fac858",
"#ee6666",
"#38AFF4",
"#03b365",
"#fc8452",
"#9a60b4",
"#ea7ccc",
];
export const CUSTOM_CHART_TYPES = [
"line",
"bar",
"pie",
"scatter",
"effectScatter",
"radar",
"tree",
"treemap",
"sunburst",
"boxplot",
"candlestick",
"heatmap",
"map",
"parallel",
"lines",
"graph",
"sankey",
"funnel",
"gauge",
"pictorialBar",
"themeRiver",
"custom",
];
type Feature = {
type: string;
properties: any;
};
export interface RegisterMapData {
type: string;
features: Feature[];
}
export const CUSTOM_CHART_DEFAULT_PARSED = {
type: "",
xAxis: {},
yAxis: {},
series: [],
};
export enum LabelOrientation {
AUTO = "auto",
SLANT = "slant",
ROTATE = "rotate",
STAGGER = "stagger",
}
export enum AxisType {
CATEGORY = "category",
VALUE = "value",
TIME = "time",
LOG = "log",
}
export const LABEL_ORIENTATION_COMPATIBLE_CHARTS = [
"LINE_CHART",
"AREA_CHART",
"COLUMN_CHART",
];
export const ECHART_TYPE_MAP: any = {
LINE_CHART: "line",
BAR_CHART: "bar",
PIE_CHART: "pie",
COLUMN_CHART: "bar",
AREA_CHART: "line",
SCATTER_CHART: "scatter",
CUSTOM_CHART: "custom",
};
export const NO_AXIS: any = {
PIE_CHART: 1,
};
export const isLabelOrientationApplicableFor = (chartType: string) =>
LABEL_ORIENTATION_COMPATIBLE_CHARTS.includes(chartType);
export const ECHART_BASIC_OPTION = {
xAxis: {
type: "category",
},
animation: true,
yAxis: {
type: "value",
},
series: [],
};

View File

@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="100" cy="99.9414" rx="96.5651" ry="96.5075" transform="rotate(-0.2588 100 99.9414)" fill="#4b4848" />
<path
d="M83.506 43C94.8782 43 104.885 49.2273 114.334 56.7415C132.077 70.8503 137.701 97.0118 127.501 118.08C126.498 120.15 126.514 119.704 125.586 121.803C122.995 127.658 124.89 133.591 130.229 136.685C132.011 137.718 133.953 138.215 135.874 138.215C139.357 138.215 142.775 136.58 145.057 133.537C148.82 128.52 148.171 122.48 143.298 118.08C142.117 117.013 140.698 116.207 138.788 114.857C141.224 113.608 143.585 113.013 145.733 113.013C150.48 113.013 154.184 115.92 155.334 121.104C156.093 124.528 157.024 126.618 160.713 128.043C168.408 131.016 169.893 143.664 165.715 145.462C165.665 145.484 165.612 145.494 165.558 145.494C165.024 145.494 164.315 144.521 163.995 143.849C161.269 138.14 157.497 135.162 153.184 135.162C150.324 135.162 147.225 136.472 144.039 139.165C133.964 147.679 122.932 153.92 109.71 156.066C105.835 156.695 102.005 157 98.2425 157C65.9789 157 38.8531 134.554 33.6656 101.604C30.3265 80.397 39.7375 60.1715 57.7121 49.9243C65.8455 45.2876 74.707 42.9996 83.506 43ZM135.093 128.38C134.468 128.017 133.612 127.521 134.429 125.676C134.647 125.183 134.921 124.646 135.223 124.079C135.88 124.518 136.55 124.967 136.805 125.197C138.267 126.518 137.867 127.051 137.314 127.788C136.782 128.498 136.183 128.625 135.874 128.604C135.671 128.589 135.353 128.53 135.093 128.38Z"
fill="#ffffff" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,94 @@
import Widget from "./widget";
import IconSVG from "./icon.svg";
import { generateReactKey } from "widgets/WidgetUtils";
import { ECHART_TYPE_MAP } from "./constants";
const DEFAUTL_CHART = {
name: "",
chartName: "秋季系列",
chartType: "LINE_CHART",
type: ECHART_TYPE_MAP["LINE_CHART"],
data: [
{
value: 335,
name: "衬衫",
},
{
value: 234,
name: "羊毛衫",
},
{
value: 1548,
name: "雪纺衫",
},
{
value: 758,
name: "裤子",
},
{
value: 358,
name: "高跟鞋",
},
{
value: 658,
name: "袜子",
},
],
xAxisName: "品类",
yAxisName: "销量",
};
const defaultKey = generateReactKey();
export const CONFIG = {
type: Widget.getWidgetType(),
name: "Echart", // The display name which will be made in uppercase and show in the widgets panel ( can have spaces )
iconSVG: IconSVG,
needsMeta: true, // Defines if this widget adds any meta properties
searchTags: ["graph", "echart", "chart", "visualisations"],
defaults: {
widgetName: "Echart",
mycustom: 2,
rows: 32,
columns: 24,
version: 1,
chartData: {
[defaultKey]: {
type: DEFAUTL_CHART.type,
seriesName: DEFAUTL_CHART.chartName,
data: DEFAUTL_CHART.data,
},
},
chartType: DEFAUTL_CHART.chartType,
chartName: DEFAUTL_CHART.name,
xAxisName: DEFAUTL_CHART.xAxisName,
yAxisName: DEFAUTL_CHART.yAxisName,
allowScroll: false,
animateLoading: true,
customEchartConfig: {
title: {
text: "基础折线图",
},
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: "line",
},
],
},
},
properties: {
derived: Widget.getDerivedPropertiesMap(),
default: Widget.getDefaultPropertiesMap(),
meta: Widget.getMetaPropertiesMap(),
config: Widget.getPropertyPaneConfig(),
contentConfig: Widget.getPropertyPaneContentConfig(),
styleConfig: Widget.getPropertyPaneStyleConfig(),
},
};
export default Widget;

View File

@ -0,0 +1,30 @@
/* eslint-disable @typescript-eslint/ban-types */
import * as _ from "lodash";
function updater_customer(params: string) {
return new Function("return " + params)();
}
/**
* convert string function to funciton
* @param data object with string function
* @returns object with function
*/
export function convertStringFunciton(temp: any) {
const data = _.cloneDeep(temp);
function traverse(params: any, path: string) {
for (const key in params) {
const current_path = `${path}${path ? "." : ""}${key}`;
if (
typeof params[key] === "string" &&
params[key].trimStart().startsWith("function")
) {
_.update(data, current_path, updater_customer);
} else if (typeof params[key] === "object") {
traverse(params[key], current_path);
}
}
}
traverse(temp, "");
return data;
}

View File

@ -0,0 +1,130 @@
import React, { lazy, Suspense } from "react";
// import styled from "styled-components";
import * as _ from "lodash";
import Skeleton from "components/utils/Skeleton";
import propertyConfig, { contentConfig, styleConfig } from "./propertyConfig";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
import { retryPromise } from "utils/AppsmithUtils";
import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget";
import { DerivedPropertiesMap } from "utils/WidgetFactory";
import { Colors } from "constants/Colors";
import { AllChartData, ChartType } from "../constants";
// import { computeFinalRowCols } from "components/editorComponents/ResizableUtils";
// import { EchartComponentProps } from "../component";
const EchartComponent = lazy(() =>
retryPromise(() =>
import(
/* webpackPrefetch: true, webpackChunkName: "charts" */ "../component"
),
),
);
class EchartWidget extends BaseWidget<EchartWidgetProps, WidgetState> {
static getPropertyPaneConfig() {
return propertyConfig;
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {};
}
static getPropertyPaneContentConfig() {
return contentConfig;
}
static getPropertyPaneStyleConfig() {
return styleConfig;
}
static getMetaPropertiesMap(): Record<string, any> {
return {
selectedDataItem: undefined,
instance: null,
};
}
onDataPointClick = (selectedDataItem: any) => {
this.props.updateWidgetMetaProperty(
"selectedDataItem",
_.omit(selectedDataItem, "event"),
{
triggerPropertyName: "onDataPointClick",
dynamicString: this.props.onDataPointClick,
event: {
type: EventType.ON_DATA_POINT_CLICK,
},
},
);
};
getEchartInstance = (instance: any) => {
const params = {
name: "instance",
type: "custom",
id: instance.id,
};
this.props.updateWidgetMetaProperty("instance", params);
};
getPageView() {
// console.log(this.props.customEchartConfig, "echart");
return (
<Suspense fallback={<Skeleton />}>
<EchartComponent
allowScroll={this.props.allowScroll}
borderRadius={this.props.borderRadius}
boxShadow={this.props.boxShadow}
chartData={this.props.chartData}
chartName={this.props.chartName}
chartType={this.props.chartType}
customEchartConfig={this.props.customEchartConfig}
fontFamily={this.props.fontFamily ?? "Nunito Sans"}
isLoading={this.props.isLoading}
isVisible={this.props.isVisible}
key={this.props.widgetId}
labelOrientation={this.props.labelOrientation}
primaryColor={this.props.accentColor ?? Colors.ROYAL_BLUE_2}
backgroundColor={this.props.backgroundColor}
setAdaptiveYMin={this.props.setAdaptiveYMin}
widgetId={this.props.widgetId}
xAxisName={this.props.xAxisName}
yAxisName={this.props.yAxisName}
onDataPointClick={this.onDataPointClick}
onInstance={this.getEchartInstance}
registerMapName={this.props.registerMapName}
registerMapJsonUrl={this.props.registerMapJsonUrl}
/>
</Suspense>
);
}
static getWidgetType(): string {
return "ECHART_WIDGET";
}
}
export interface EchartWidgetProps extends WidgetProps {
chartType: ChartType;
chartData: AllChartData;
customEchartConfig: any;
xAxisName: string;
yAxisName: string;
chartName: string;
isVisible?: boolean;
allowScroll: boolean;
borderRadius: string;
boxShadow?: string;
accentColor?: string;
fontFamily?: string;
backgroundColor?: string;
onDataPointClick?: string;
registerMapName?: string;
registerMapJsonUrl?: string;
}
export default EchartWidget;

View File

@ -0,0 +1,695 @@
// import { ChartWidgetProps } from "widgets/ChartWidget/widget";
import { EchartWidgetProps } from "widgets/EchartWidget/widget";
import { ValidationTypes } from "constants/WidgetValidation";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
// import { EditorModes } from "components/editorComponents/CodeEditor/EditorConfig";
import {
// CUSTOM_CHART_TYPES,
LabelOrientation,
isLabelOrientationApplicableFor,
} from "../constants";
export default [
{
sectionName: "属性",
children: [
{
helpText: "给图表添加标题",
placeholderText: "Echart 标题",
propertyName: "chartName",
label: "标题",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
helpText: "修改图表数据展示形态",
propertyName: "chartType",
label: "图表类型",
controlType: "DROP_DOWN",
options: [
{
label: "折线图",
value: "LINE_CHART",
},
{
label: "柱状图",
value: "BAR_CHART",
},
{
label: "饼图",
value: "PIE_CHART",
},
{
label: "散点图",
value: "SCATTER_CHART",
},
{
label: "自定义图表",
value: "CUSTOM_CHART",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: [
"LINE_CHART",
"BAR_CHART",
"PIE_CHART",
"SCATTER_CHART",
"CUSTOM_CHART",
],
},
},
},
{
helpText: "完整配置请查阅 Echart 官方文档",
placeholderText: `Echart 图表配置`,
propertyName: "customEchartConfig",
label: "自定义 Echart 图表",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
// mode: EditorModes.JAVASCRIPT,
validation: {
type: ValidationTypes.OBJECT,
params: {
allowedKeys: [
{
type: ValidationTypes.ARRAY,
name: "series",
params: {
default: [] || {},
required: true,
},
},
],
},
},
hidden: (props: EchartWidgetProps) =>
props.chartType !== "CUSTOM_CHART",
dependencies: ["chartType"],
evaluationSubstitutionType: EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "给图表添加数据",
propertyName: "chartData",
placeholderText: '[{ "name": "2021", "value": 94000 }]',
label: "图表序列",
controlType: "ECHART_DATA",
isBindProperty: false,
isTriggerProperty: false,
hidden: (props: EchartWidgetProps) =>
props.chartType === "CUSTOM_CHART",
dependencies: ["chartType"],
children: [
{
helpText: "Series data",
propertyName: "data",
label: "序列数据",
controlType: "INPUT_TEXT_AREA",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.ARRAY,
params: {
children: {
type: ValidationTypes.OBJECT,
params: {
required: true,
allowedKeys: [
{
name: "name",
type: ValidationTypes.TEXT,
params: {
required: true,
default: "",
},
},
{
name: "value",
type: ValidationTypes.NUMBER,
params: {
required: true,
default: 10,
},
},
],
},
},
},
},
evaluationSubstitutionType:
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "Series Name",
propertyName: "seriesName",
label: "序列名称",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
],
},
// {
// propertyName: "useDataSet",
// label: "使用数据集DataSet",
// helpText: "使用数据集设置图表数据",
// controlType: "SWITCH",
// isJSConvertible: true,
// isBindProperty: true,
// isTriggerProperty: false,
// validation: { type: ValidationTypes.BOOLEAN },
// },
{
propertyName: "isVisible",
label: "是否显示",
helpText: "控制组件的显示/隐藏",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "animateLoading",
label: "加载时显示动画",
controlType: "SWITCH",
helpText: "组件依赖的数据加载时显示加载动画",
defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
helpText: "允许图表内部滚动",
propertyName: "allowScroll",
label: "允许滚动",
controlType: "SWITCH",
isBindProperty: false,
isTriggerProperty: false,
hidden: (x: EchartWidgetProps) =>
x.chartType === "CUSTOM_CHART" || x.chartType === "PIE_CHART",
dependencies: ["chartType"],
},
],
},
{
sectionName: "坐标轴配置",
children: [
{
helpText: "设置x轴标签",
propertyName: "xAxisName",
placeholderText: "设置x轴标签",
label: "x轴标签",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
dependencies: ["chartType"],
},
{
helpText: "设置y轴标签",
propertyName: "yAxisName",
placeholderText: "设置y轴标签",
label: "y轴标签",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
dependencies: ["chartType"],
},
{
helpText: "修改x轴标签方向",
propertyName: "labelOrientation",
label: "x轴标签方向",
hidden: (x: EchartWidgetProps) =>
!isLabelOrientationApplicableFor(x.chartType),
isBindProperty: false,
isTriggerProperty: false,
dependencies: ["chartType"],
controlType: "DROP_DOWN",
options: [
{
label: "自动",
value: LabelOrientation.AUTO,
},
{
label: "倾斜",
value: LabelOrientation.SLANT,
},
{
label: "旋转",
value: LabelOrientation.ROTATE,
},
{
label: "交错",
value: LabelOrientation.STAGGER,
},
],
},
{
propertyName: "setAdaptiveYMin",
label: "自适应坐标轴",
helpText: "定义坐标轴最小刻度",
controlType: "SWITCH",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
],
},
{
sectionName: "地图注册配置",
children: [
{
propertyName: "registerMapName",
label: "地图名称",
placeholderText: "地图名称",
helpText: "地图名称",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
regex: /\w+/,
},
},
// hidden: (x: any) => x.chartType !== "CUSTOM_CHART",
dependencies: ["chartType"],
},
{
helpText: "地图注册JSON数据",
propertyName: "registerMapJsonUrl",
label: "JSON链接",
controlType: "INPUT_TEXT",
placeholderText: "注册地图JSON数据链接",
isBindProperty: true,
isTriggerProperty: false,
helpLink: "https://datav.aliyun.com/portal/school/atlas/area_selector",
validation: {
type: ValidationTypes.TEXT,
params: {
default: "",
required: false,
// regex: /(http(s?):)([/|.|\w|\s|-])*\.(?:json)/,
},
},
},
],
},
{
sectionName: "事件",
children: [
{
helpText: "点击数据点时触发",
propertyName: "onDataPointClick",
label: "onDataPointClick",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
{
sectionName: "样式",
children: [
{
propertyName: "borderRadius",
label: "边框圆角",
helpText: "边框圆角样式",
controlType: "BORDER_RADIUS_OPTIONS",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "boxShadow",
label: "阴影",
helpText: "组件轮廓投影",
controlType: "BOX_SHADOW_OPTIONS",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "backgroundColor",
helpText: "设置菜单项背景颜色",
label: "背景颜色",
controlType: "COLOR_PICKER",
isBindProperty: true,
isTriggerProperty: false,
},
],
},
];
export const contentConfig = [
{
sectionName: "数据",
children: [
{
helpText: "修改图表数据展示形态",
propertyName: "chartType",
label: "图表类型",
controlType: "DROP_DOWN",
options: [
{
label: "折线图",
value: "LINE_CHART",
},
{
label: "柱状图",
value: "BAR_CHART",
},
{
label: "饼图",
value: "PIE_CHART",
},
{
label: "散点图",
value: "SCATTER_CHART",
},
{
label: "自定义图表",
value: "CUSTOM_CHART",
},
],
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
allowedValues: [
"LINE_CHART",
"BAR_CHART",
"PIE_CHART",
"SCATTER_CHART",
"CUSTOM_CHART",
],
},
},
},
{
helpText: "完整配置请查阅 Echart 官方文档",
placeholderText: `Echart 图表配置`,
propertyName: "customEchartConfig",
label: "自定义 Echart 图表",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.OBJECT,
params: {
allowedKeys: [
{
type: ValidationTypes.ARRAY,
name: "series",
params: {
default: [] || {},
required: true,
},
},
],
},
},
hidden: (props: EchartWidgetProps) =>
props.chartType !== "CUSTOM_CHART",
dependencies: ["chartType"],
// mode: EditorModes.JAVASCRIPT,
evaluationSubstitutionType: EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
{
helpText: "给图表添加数据",
propertyName: "chartData",
placeholderText: '[{ "name": "2021", "value": 94000 }]',
label: "图表序列",
controlType: "ECHART_DATA",
isBindProperty: false,
isTriggerProperty: false,
hidden: (props: EchartWidgetProps) =>
props.chartType === "CUSTOM_CHART",
dependencies: ["chartType"],
children: [
{
helpText: "Series data",
propertyName: "data",
label: "序列数据",
controlType: "INPUT_TEXT_AREA",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.ARRAY,
params: {
children: {
type: ValidationTypes.OBJECT,
params: {
required: true,
allowedKeys: [
{
name: "name",
type: ValidationTypes.TEXT,
params: {
required: true,
default: "",
},
},
{
name: "value",
type: ValidationTypes.NUMBER,
params: {
required: true,
default: 10,
},
},
],
},
},
},
},
evaluationSubstitutionType:
EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
],
},
],
},
{
sectionName: "属性",
children: [
{
helpText: "给图表添加标题",
placeholderText: "Echart 标题",
propertyName: "chartName",
label: "标题",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "isVisible",
label: "是否显示",
helpText: "控制组件的显示/隐藏",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
propertyName: "animateLoading",
label: "加载时显示动画",
controlType: "SWITCH",
helpText: "组件依赖的数据加载时显示加载动画",
defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
helpText: "允许图表内部滚动",
propertyName: "allowScroll",
label: "允许滚动",
controlType: "SWITCH",
isBindProperty: false,
isTriggerProperty: false,
hidden: (x: EchartWidgetProps) =>
x.chartType === "CUSTOM_CHART" || x.chartType === "PIE_CHART",
dependencies: ["chartType"],
},
],
},
{
sectionName: "坐标轴配置",
children: [
{
propertyName: "setAdaptiveYMin",
label: "自适应坐标轴",
helpText: "定义坐标轴最小刻度",
controlType: "SWITCH",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
{
helpText: "设置x轴标签",
propertyName: "xAxisName",
placeholderText: "日期",
label: "x轴标签",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
hidden: (x: any) => x.chartType === "CUSTOM_CHART",
dependencies: ["chartType"],
},
{
helpText: "设置y轴标签",
propertyName: "yAxisName",
placeholderText: "收入",
label: "y轴标签",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
hidden: (x: any) => x.chartType === "CUSTOM_CHART",
dependencies: ["chartType"],
},
// {
// helpText: "修改x轴标签方向",
// propertyName: "labelOrientation",
// label: "x轴标签方向",
// hidden: (x: EchartWidgetProps) =>
// !isLabelOrientationApplicableFor(x.chartType),
// isBindProperty: false,
// isTriggerProperty: false,
// dependencies: ["chartType"],
// controlType: "DROP_DOWN",
// options: [
// {
// label: "自动",
// value: LabelOrientation.AUTO,
// },
// {
// label: "倾斜",
// value: LabelOrientation.SLANT,
// },
// {
// label: "旋转",
// value: LabelOrientation.ROTATE,
// },
// {
// label: "交错",
// value: LabelOrientation.STAGGER,
// },
// ],
// },
],
},
{
sectionName: "地图注册配置",
children: [
{
propertyName: "registerMapName",
label: "地图名称",
helpText: "地图名称",
placeholderText: "地图名称",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
validation: {
type: ValidationTypes.TEXT,
params: {
regex: /\w+/,
},
},
// hidden: (x: any) => x.chartType === "CUSTOM_CHART",
dependencies: ["chartType"],
},
{
helpText: "地图注册JSON数据",
propertyName: "registerMapJsonUrl",
label: "JSON链接",
controlType: "INPUT_TEXT",
placeholderText: "注册地图JSON数据链接",
isBindProperty: true,
isTriggerProperty: false,
helpLink: "https://datav.aliyun.com/portal/school/atlas/area_selector",
validation: {
type: ValidationTypes.TEXT,
params: {
default: "",
required: false,
// regex: /(http(s?):)([/|.|\w|\s|-])*\.(?:json)/,
},
},
},
],
},
{
sectionName: "事件",
children: [
{
helpText: "点击数据点时触发",
propertyName: "onDataPointClick",
label: "onDataPointClick",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];
export const styleConfig = [
{
sectionName: "轮廓样式",
children: [
{
propertyName: "borderRadius",
label: "边框圆角",
helpText: "边框圆角样式",
controlType: "BORDER_RADIUS_OPTIONS",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "boxShadow",
label: "阴影",
helpText: "组件轮廓投影",
controlType: "BOX_SHADOW_OPTIONS",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
validation: { type: ValidationTypes.TEXT },
},
{
propertyName: "backgroundColor",
helpText: "设置菜单项背景颜色",
label: "背景颜色",
controlType: "COLOR_PICKER",
isBindProperty: true,
isTriggerProperty: false,
},
],
},
];

View File

@ -334,6 +334,7 @@ export const CONFIG = {
"BUTTON_GROUP_WIDGET",
"BUTTON_WIDGET",
"CHART_WIDGET",
"ECHART_WIDGET",
"CHECKBOX_WIDGET",
"CHECKBOX_GROUP_WIDGET",
"DIVIDER_WIDGET",

View File

@ -81,6 +81,22 @@ export const DATA_TREE_FUNCTIONS: Record<
executionType: ExecutionType.PROMISE,
};
},
callFunc: {
qualifier: (entity) => isAppsmithEntity(entity),
path: "appsmith.echartInstance.callFunc",
func: () =>
function(widgetName: string, funcName: string, options?: any) {
return {
type: ActionTriggerType.CALL_FUNC,
payload: {
widgetName,
funcName,
options,
},
executionType: ExecutionType.PROMISE,
};
},
},
storeValue: function(key: string, value: string, persist = true) {
// momentarily store this value in local state to support loops
_.set(self, ["appsmith", "store", key], value);

View File

@ -257,6 +257,7 @@ $(if [[ $use_https == 1 ]]; then echo "
sub_filter __APPSMITH_DISABLE_INTERCOM__ '${APPSMITH_DISABLE_INTERCOM-}';
sub_filter __APPSMITH_FORM_LOGIN_DISABLED__ '${APPSMITH_FORM_LOGIN_DISABLED-}';
sub_filter __APPSMITH_SIGNUP_DISABLED__ '${APPSMITH_SIGNUP_DISABLED-}';
sub_filter __BMAP_AK__ '${BMAP_AK-}';
sub_filter __APPSMITH_ZIPY_SDK_KEY__ '${APPSMITH_ZIPY_SDK_KEY-}';
sub_filter __APPSMITH_HIDE_WATERMARK__ '${APPSMITH_HIDE_WATERMARK-}';
sub_filter __APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX__ '${APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX-}';

View File

@ -0,0 +1,61 @@
import * as Factory from "factory.ts";
import { generateReactKey } from "utils/generators";
import { WidgetProps } from "widgets/BaseWidget";
export const EchartFactory = Factory.Sync.makeFactory<WidgetProps>({
isVisible: true,
chartType: "LINE_CHART",
chartName: "Sales on working days",
allowScroll: false,
chartData: [
{
seriesName: "Sales",
data: [
{
x: "Mon",
y: 10000,
},
{
x: "Tue",
y: 12000,
},
{
x: "Wed",
y: 32000,
},
{
x: "Thu",
y: 28000,
},
{
x: "Fri",
y: 14000,
},
{
x: "Sat",
y: 19000,
},
{
x: "Sun",
y: 36000,
},
],
},
],
xAxisName: "Last Week",
yAxisName: "Total Order Revenue $",
type: "ECHART_WIDGET",
isLoading: false,
parentColumnSpace: 71.75,
parentRowSpace: 38,
leftColumn: 2,
rightColumn: 8,
topRow: 1,
bottomRow: 9,
parentId: "rglduihhzk",
dynamicBindingPathList: [],
widgetName: Factory.each((i) => `Echart${i + 1}`),
widgetId: generateReactKey(),
renderMode: "CANVAS",
version: 1,
});

View File

@ -13,6 +13,7 @@ import { OldTabsFactory, TabsFactory } from "./TabsFactory";
import { ModalFactory } from "./ModalFactory";
import { RichTextFactory } from "./RichTextFactory";
import { ChartFactory } from "./ChartFactory";
import { EchartFactory } from "./EchartFactory";
import { FormFactory } from "./FormFactory";
import { FormButtonFactory } from "./FormButtonFactory";
import { MapFactory } from "./MapFactory";
@ -42,6 +43,7 @@ export const WidgetTypeFactories: Record<string, any> = {
MODAL_WIDGET: ModalFactory,
RICH_TEXT_EDITOR_WIDGET: RichTextFactory,
CHART_WIDGET: ChartFactory,
ECHART_WIDGET: EchartFactory,
FORM_WIDGET: FormFactory,
FORM_BUTTON_WIDGET: FormButtonFactory,
MAP_WIDGET: MapFactory,

View File

@ -34,3 +34,4 @@ APPSMITH_CODEC_SIZE=10
#APPSMITH_RECAPTCHA_SITE_KEY=""
#APPSMITH_RECAPTCHA_SECRET_KEY=""
BMAP_AK="nWCpSjRnXLfGuBc3iLZ9kYv8Y6wYaxf8"

View File

@ -69,6 +69,8 @@ public class Application extends BaseDomain {
String icon;
String chartTheme;
private String slug;
AppLayout unpublishedAppLayout;
@ -163,6 +165,7 @@ public class Application extends BaseDomain {
this.clonedFromApplicationId = application.getId();
this.color = application.getColor();
this.icon = application.getIcon();
this.chartTheme = application.getChartTheme();
this.unpublishedAppLayout = application.getUnpublishedAppLayout() == null ? null : new AppLayout(application.getUnpublishedAppLayout().type);
this.publishedAppLayout = application.getPublishedAppLayout() == null ? null : new AppLayout(application.getPublishedAppLayout().type);
this.viewerLayout = application.getViewerLayout();

View File

@ -37,3 +37,4 @@ APPSMITH_CLOUD_SERVICES_BASE_URL="https://release-cs.appsmith.com"
#APPSMITH_RECAPTCHA_SITE_KEY=""
#APPSMITH_RECAPTCHA_SECRET_KEY=""
BMAP_AK="nWCpSjRnXLfGuBc3iLZ9kYv8Y6wYaxf8"

View File

@ -59,6 +59,7 @@ $NGINX_SSL_CMNT server_name $custom_domain ;
sub_filter __APPSMITH_DISABLE_INTERCOM__ '\${APPSMITH_DISABLE_INTERCOM}';
sub_filter __APPSMITH_FORM_LOGIN_DISABLED__ '\${APPSMITH_FORM_LOGIN_DISABLED}';
sub_filter __APPSMITH_SIGNUP_DISABLED__ '\${APPSMITH_SIGNUP_DISABLED}';
sub_filter __BMAP_AK__ '\${BMAP_AK}';
}