Merge branch 'feats/security'
eapim-portal CI / build (push) Has been cancelled
eapim-portal Test / test (push) Has been cancelled

This commit is contained in:
Rinjae
2026-08-27 15:43:42 +09:00
67 changed files with 789 additions and 11893 deletions
+67 -11
View File
@@ -83,7 +83,10 @@ dependencies {
implementation group: 'com.fasterxml.woodstox', name: 'woodstox-core', version: '6.5.1' implementation group: 'com.fasterxml.woodstox', name: 'woodstox-core', version: '6.5.1'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // Thymeleaf 3.1 코어가 #temporals 를 내장 제공 → java8time extras 를 함께 두면 표현식 객체가 중복 등록된다.
implementation('org.springframework.boot:spring-boot-starter-thymeleaf') {
exclude group: 'org.thymeleaf.extras', module: 'thymeleaf-extras-java8time'
}
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
implementation('org.springframework.boot:spring-boot-starter-cache') implementation('org.springframework.boot:spring-boot-starter-cache')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
@@ -91,33 +94,40 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-jdbc'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework:spring-expression:5.3.30' // spring-expression 개별 pin 제거: 아래 ext 의 spring-framework.version 이 전 모듈을 일괄 관리한다.
// 개별 pin 이 남아 있으면 다른 spring-* 모듈보다 낮은 버전으로 고정되어 버전이 어긋난다.
implementation group: 'xalan', name: 'xalan', version: '2.7.3' implementation group: 'xalan', name: 'xalan', version: '2.7.3'
implementation 'org.hibernate:hibernate-envers:5.6.15.Final' implementation 'org.hibernate:hibernate-envers:5.6.15.Final'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.0.0' implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.4.0' // 3.0.0 은 thymeleaf 3.0 전용
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'com.github.ua-parser:uap-java:1.5.3' // uap-java 제거: 소스 전체에 ua_parser 참조 0건이고, 1.5.3 은 snakeyaml 2.x 에서 삭제된
// SafeConstructor() no-arg 를 호출해 NoSuchMethodError 를 낸다. 되살릴 경우 1.6.1 이상.
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1' implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1'
implementation 'javax.servlet:javax.servlet-api:4.0.0' implementation 'javax.servlet:javax.servlet-api:4.0.0'
implementation 'org.jasypt:jasypt:1.9.3' implementation 'org.jasypt:jasypt:1.9.3'
implementation 'xerces:xercesImpl:2.12.2' implementation 'xerces:xercesImpl:2.12.2'
implementation 'org.apache.commons:commons-lang3:3.12.0' // Uncontrolled recursion in ClassUtils.getClass(...) on very long inputs. 3.18.0+ 에서 수정.
implementation 'org.apache.commons:commons-lang3:3.20.0'
implementation 'org.apache.commons:commons-collections4:4.4' implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'commons-net:commons-net:3.9.0' implementation 'commons-net:commons-net:3.9.0'
implementation('commons-beanutils:commons-beanutils:1.9.4') { // CVE-2025-48734 (PropertyUtilsBean 이 enum 의 declaredClass 프로퍼티 노출 → ClassLoader 접근/RCE).
// 1.11.0 부터 SuppressPropertiesBeanIntrospector 가 기본 활성이라 declaredClass 접근이 차단된다.
// 이 앱의 호출부(PasswordMatchValidator / PasswordRuleValidator / AuthNumberValidator)는
// 어노테이션에 박힌 고정 프로퍼티명만 넘기므로 외부 입력 경로는 없지만 버전은 올려 둔다.
// 1.11.0 = Java 8 바이트코드(major 52), PropertyUtils.getProperty/getNestedProperty API 동일.
implementation('commons-beanutils:commons-beanutils:1.11.0') {
// exclude group: 'commons-collections', module: 'commons-collections' // exclude group: 'commons-collections', module: 'commons-collections'
} }
implementation 'org.mapstruct:mapstruct:1.5.5.Final' implementation 'org.mapstruct:mapstruct:1.5.5.Final'
// WS-2026-0003 (jackson-core async parser DoS, CVSS 7.5) — 2.18.6 에서 수정. JDK8 호환. // jackson 개별 pin 제거: 아래 ext 의 jackson-bom.version 이 전 모듈을 일괄 관리한다.
implementation 'com.fasterxml.jackson.core:jackson-core:2.18.6' // 개별 pin 은 BOM 보다 우선하므로 남겨 두면 BOM 만 올렸을 때 core/annotations/databind 가
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.6' // 옛 버전에 고정돼 버전이 어긋난다(실제로 그런 상태였다).
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.6'
implementation group: 'org.apache.velocity', name: 'velocity-engine-core', version: '2.3' implementation group: 'org.apache.velocity', name: 'velocity-engine-core', version: '2.3'
@@ -127,7 +137,10 @@ dependencies {
implementation 'net.bytebuddy:byte-buddy:1.14.5' implementation 'net.bytebuddy:byte-buddy:1.14.5'
// Commons FileUpload (WAS 독립적인 multipart 처리) // Commons FileUpload (WAS 독립적인 multipart 처리)
implementation 'commons-fileupload:commons-fileupload:1.5' // CVE-2025-48976 (멀티파트 파트 헤더 크기 제한 부재 → DoS). 1.6.0 에서 partHeaderSizeMax 도입.
// 주의: 1.6 부터 파트 헤더 총량 기본 상한이 10240 → 512 바이트로 줄었다(DEFAULT_PART_HEADER_SIZE_MAX).
// 한글 파일명은 UTF-8 로 3바이트/자라 Content-Disposition 이 길어질 수 있어 실측으로 여유를 확인했다.
implementation 'commons-fileupload:commons-fileupload:1.6.0'
implementation 'commons-io:commons-io:2.15.1' implementation 'commons-io:commons-io:2.15.1'
@@ -145,6 +158,49 @@ ext {
springMavenArtifactVersion = '5.3.30' springMavenArtifactVersion = '5.3.30'
encoding = 'UTF-8' encoding = 'UTF-8'
profile = 'local' profile = 'local'
// 내장 Tomcat 버전 상향 (Spring Boot 2.7.18 기본값 9.0.83 → 9.0.120).
// Boot BOM 의 tomcat.version 프로퍼티를 덮어써서 tomcat-embed-core/-el/-websocket 이 함께 올라간다.
// 9.0.x 계열 유지 = Servlet 4.0 / javax.* 네임스페이스 그대로, JDK8 호환.
set('tomcat.version', '9.0.120')
// CVE-2022-1471 (snakeyaml Constructor 임의 타입 역직렬화 → RCE). Boot 2.7.18 BOM 기본값 1.30 → 2.6.
// 앱/프레임워크 실경로는 이미 SafeConstructor 계열(OriginTrackedYamlLoader, spring-beans
// FilteringConstructor, swagger-parser DeserializationUtils)이라 익스플로잇 경로는 없었으나
// SCA 는 버전으로 판정하므로 2.x 로 올린다. snakeyaml 2.6 = Java 8 바이트코드(major 52).
// 2.6 인 이유: swagger-core 2.2.52 / swagger-parser-v3 2.1.45 가 요구하는 버전이라 강등이 없다.
// 부수 효과: swagger-parser 가 호출하는 LoaderOptions.setCodePointLimit(1.32+ API) 도 해소.
set('snakeyaml.version', '2.6')
// jackson 전 모듈 버전 통일(Boot 2.7.18 BOM 기본 2.13.5). 2.18.x 는 JDK8 호환 라인이다.
// 이유 3가지
// 1) snakeyaml 2.x 는 ParserImpl(StreamReader) 를 제거했고 jackson-dataformat-yaml 은
// 2.15+ 부터 ParserImpl(StreamReader, LoaderOptions) 를 쓴다 — 위 snakeyaml 상향의 전제.
// 2) WS-2026-0003 (jackson-core async parser DoS, CVSS 7.5) — 2.18.6 에서 수정.
// 3) jackson-databind PolymorphicTypeValidator 우회(제네릭 타입 인자 미검증) — 2.18.8 에서 수정.
// 이 앱은 다형성 역직렬화(activateDefaultTyping/@JsonTypeInfo)를 쓰지 않아 노출 경로는 없다.
// 2.18.x 마지막 패치를 쓴다.
set('jackson-bom.version', '2.18.10')
// Thymeleaf SSTI (≤3.1.3.RELEASE: 표현식 접근 객체 제한 우회 → 템플릿 인젝션). 3.0.x 는 EOL 이라
// 백포트가 없어 3.1.4 로 올린다. JDK8/Spring5 유지: thymeleaf 3.1.4 / thymeleaf-spring5 3.1.4 /
// extras-springsecurity5 3.1.5 / layout-dialect 3.4.0 모두 Java8 바이트코드(major 52), 패키지도
// org.thymeleaf.spring5 + javax.servlet 그대로다.
// Boot 2.7 ThymeleafAutoConfiguration 이 호출하는 setter 는 3.1.4 에 전부 존재함(확인함).
// 주의: 3.1 은 #request/#session/#response/#servletContext 표현식 객체를 제거했다(IllegalArgumentException).
set('thymeleaf.version', '3.1.4.RELEASE')
set('thymeleaf-extras-springsecurity.version', '3.1.5.RELEASE')
// Spring Framework 5.3.x OSS 마지막 릴리스로 통일(Boot 2.7.18 BOM 기본 5.3.31, 일부 5.3.30 혼재였음).
// 5.3.32~5.3.39 구간 CVE 정리용. 5.3.x 는 OSS EOL 이라 이 위로는 상용(Enterprise) 빌드뿐이다.
// 남는 5.3.x 지적(CVE-2026-41855 JMS Jackson 역직렬화, CVE-2016-1000027 HttpInvoker)은
// 5.3 계열에 수정본이 없고 앱이 JMS/HttpInvoker 를 쓰지 않으므로 억제 항목으로 따로 관리한다.
set('spring-framework.version', '5.3.39')
// Spring Security 5.7.x OSS 마지막 릴리스(Boot 2.7.18 BOM 기본 5.7.11).
// CVE-2026-22732(응답 커밋 후 보안 헤더 미기록)는 수정본이 5.7.22/5.8.24 = Enterprise 전용이라
// OSS 로는 올릴 수 없다. 우회책(HeaderWriterFilter.shouldWriteHeadersEagerly=true)은 동작 변경이라 분리 검토.
set('spring-security.version', '5.7.14')
} }
+90
View File
@@ -28,4 +28,94 @@
</suppress> </suppress>
--> -->
<!-- ================================================================================
Spring 계열 Critical 5건 (2026-08-18 리포트 기준).
공통 배경: Spring Framework 5.3.x / Spring Boot 2.7.x / Spring Security 5.7.x 는 모두 OSS EOL 이라
남은 수정본이 상용(Enterprise/Tanzu)뿐이다. JDK 8 유지 제약상 Boot 3 + Spring 6 이관 전까지는
버전 상향으로 못 없앤다. 아래는 "코드에 트리거 경로가 없음"을 근거로 한 억제이며 만료일을 둔다.
버전 범위를 5.3.x / 2.7.x / 5.7.x 로 고정해 두었으므로, 이관 후에는 억제가 자동으로 풀린다.
재검토 시 확인할 것: (1) 각 근거 grep 이 여전히 0건인지 (2) OSS 수정본이 나왔는지.
================================================================================ -->
<!-- 1. Spring Boot: Cassandra SSL 호스트명 미검증 -->
<suppress until="2027-02-28Z">
<notes><![CDATA[
CVE-2026-40974 는 Spring Boot 의 Cassandra SSL 자동설정이 SSL 번들의 호스트명 검증 설정을
드라이버에 전달하지 않는 문제다. 이 앱은 Cassandra 를 쓰지 않는다
(runtimeClasspath 에 cassandra/datastax 계열 jar 0건, spring-boot-starter-data-cassandra 미선언).
수정본 2.7.33 은 상용(Enterprise) 릴리스라 OSS 로는 올릴 수 없다.
devtools/actuator/spring-boot-admin jar 는 스캔 대상(runtimeClasspath)에는 있으나
배포 산출물에서는 build.gradle 의 localOnlyLibPrefixes 로 제외되어 WAR 에 실리지 않는다.
확인: Rinjae / 2026-08-18 / grep -ri cassandra 0건, WAR 내 devtools/actuator jar 0건.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.springframework\.boot/.*@2\.7\..*$</packageUrl>
<cve>CVE-2026-40974</cve>
</suppress>
<!-- 2. Thymeleaf SSTI 2건: starter jar 이름 기준 오탐 -->
<suppress>
<notes><![CDATA[
오탐. CVE-2026-40477 / CVE-2026-40478 은 thymeleaf 본체 3.1.3.RELEASE 이하의 표현식 샌드박스
우회 문제이고 3.1.4.RELEASE 에서 수정됐다. 이 프로젝트는 build.gradle 의 ext 에서
thymeleaf.version=3.1.4.RELEASE / thymeleaf-extras-springsecurity.version=3.1.5.RELEASE 로
올려 두었으므로 실제 실리는 jar 는 thymeleaf-3.1.4.RELEASE.jar 다.
Dependency-Check 가 spring-boot-starter-thymeleaf-2.7.18.jar(의존만 선언한 빈 starter)에
thymeleaf CPE 를 잘못 매칭한 결과다. thymeleaf 본체 jar 에 대한 탐지는 억제하지 않는다.
확인: Rinjae / 2026-08-18 / WAR 내 thymeleaf-3.1.4.RELEASE.jar, thymeleaf-spring5-3.1.4.RELEASE.jar.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.springframework\.boot/spring-boot-starter-thymeleaf@.*$</packageUrl>
<cve>CVE-2026-40477</cve>
<cve>CVE-2026-40478</cve>
</suppress>
<!-- 3. Spring Framework: JMS Jackson 역직렬화 -->
<suppress until="2027-02-28Z">
<notes><![CDATA[
CVE-2026-41855 는 org.springframework.jms.support.converter.MappingJackson2MessageConverter /
JacksonJsonMessageConverter 가 임의 클래스 인스턴스화를 허용하는 문제다(신뢰할 수 없는 JMS 환경 전제).
이 앱은 JMS 를 쓰지 않는다: runtimeClasspath 에 spring-jms 0건, 소스에 javax.jms /
JmsTemplate / MappingJackson2MessageConverter 참조 0건.
5.3.x 는 OSS 수정본이 없다(6.2.19 / 7.0.8 에서만 수정). 이미 5.3.x OSS 마지막인 5.3.39 로 올려 둔 상태다.
확인: Rinjae / 2026-08-18 / grep -rn "javax.jms|JmsTemplate|MappingJackson2MessageConverter" src 0건.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.springframework/spring-.*@5\.3\..*$</packageUrl>
<cve>CVE-2026-41855</cve>
</suppress>
<!-- 4. spring-web: HttpInvoker 역직렬화 -->
<suppress until="2027-02-28Z">
<notes><![CDATA[
CVE-2016-1000027 은 HttpInvokerServiceExporter 를 노출했을 때만 성립한다. 해당 클래스는
Spring 6.0 에서 제거됐고 5.3.x 에는 수정본이 없다(= 5.3.x 를 쓰는 한 계속 탐지된다).
이 앱은 HttpInvoker 계열을 쓰지 않는다: 소스에 HttpInvoker 참조 0건이고 외부 호출은
RestTemplate / HttpClient 기반이다.
근본 해결은 Spring 6(Boot 3, JDK 17) 이관. 이관 전까지 억제한다.
확인: Rinjae / 2026-08-18 / grep -rn HttpInvoker src 0건.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.springframework/spring-web@5\.3\..*$</packageUrl>
<cve>CVE-2016-1000027</cve>
</suppress>
<!-- 5. Spring Security: 응답 커밋 시 보안 헤더 미기록 -->
<suppress until="2027-02-28Z">
<notes><![CDATA[
CVE-2026-22732 는 응답이 커밋된 뒤 Spring Security 가 보안 헤더를 기록하지 못하는 문제다.
트리거는 Content-Length 를 setHeader / setIntHeader / addIntHeader 로 지정하는 경로다
(OnCommittedResponseWrapper 는 setContentLength / setContentLengthLong / addHeader 만 추적한다).
(1) 이 코드베이스에는 트리거가 없다: portal / elink-portal-common / elink-online-core-jpa 전체에
setIntHeader, addIntHeader, setHeader("Content-Length") 0건. 파일 다운로드는
response.setContentLength(int) 를 쓰며 실측상 보안 헤더가 정상 기록된다.
(2) 그럼에도 안전망으로 PortalConfigSecurity 에서
HeaderWriterFilter.setShouldWriteHeadersEagerly(true) 를 적용해(spring.io 권고 우회책)
요청 시작 시점에 헤더를 기록하도록 했다.
수정본 5.7.22 / 5.8.24 는 Enterprise 전용이라 OSS 로는 올릴 수 없다. 이미 5.7.x OSS 마지막인
5.7.14 로 올려 둔 상태다.
확인: Rinjae / 2026-08-18 / 동일 스택(Boot 2.7.18 + Security 5.7.14 + Tomcat 9.0.120) 프로브 앱 실측 —
우회책 미적용 시 setHeader/setIntHeader/addIntHeader 경로에서 헤더 누락 재현, 적용 후 정상 기록.
]]></notes>
<packageUrl regex="true">^pkg:maven/org\.springframework\.security/spring-security-.*@5\.7\..*$</packageUrl>
<cve>CVE-2026-22732</cve>
</suppress>
</suppressions> </suppressions>
@@ -36,6 +36,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -155,10 +156,10 @@ public class ApiSpecService {
updateReferences(pathItem, apiId); updateReferences(pathItem, apiId);
String tmpFullPath = StringUtils.isNotEmpty(basePath) String tmpFullPath = StringUtils.isNotEmpty(basePath)
? StringUtils.join(basePath, "/", StringUtils.removeStart(path, "/")) ? StringUtils.join(basePath, "/", Strings.CS.removeStart(path, "/"))
: path; : path;
final String fullPath = StringUtils.replacePattern(tmpFullPath, "//+", "/"); final String fullPath = tmpFullPath.replaceAll("//+", "/");
// HTTP 메소드별로 중복 체크 // HTTP 메소드별로 중복 체크
pathItem.readOperationsMap().forEach((httpMethod, operation) -> { pathItem.readOperationsMap().forEach((httpMethod, operation) -> {
@@ -294,7 +295,7 @@ public class ApiSpecService {
return StringUtils.EMPTY; return StringUtils.EMPTY;
} }
Server server = api.getServers().get(0); Server server = api.getServers().get(0);
return StringUtils.removeEnd(server.getUrl(), "/"); return Strings.CS.removeEnd(server.getUrl(), "/");
} }
private void mergeComponents(OpenAPI currentAPI, String apiId, String apiName, Map<String, Object> mergedComponents, ObjectMapper objectMapper) { private void mergeComponents(OpenAPI currentAPI, String apiId, String apiName, Map<String, Object> mergedComponents, ObjectMapper objectMapper) {
@@ -2,6 +2,7 @@ package com.eactive.apim.portal.common.exception;
import java.util.Arrays; import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@@ -36,23 +37,27 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@RequiredArgsConstructor @RequiredArgsConstructor
public class PortalGlobalExceptionHandler { public class PortalGlobalExceptionHandler {
private static final String REDIRECT_PREFIX = "redirect:";
private static final String REDIRECT_HOME = REDIRECT_PREFIX + "/";
private static final String REDIRECT_LOGIN_AUTH = REDIRECT_PREFIX + "/login?reason=auth";
private final Logger log = LoggerFactory.getLogger(getClass()); private final Logger log = LoggerFactory.getLogger(getClass());
private final PortalProperties portalProperties; private final PortalProperties portalProperties;
private final Environment environment; private final Environment environment;
@ExceptionHandler(value = NotFoundException.class) @ExceptionHandler(value = NotFoundException.class)
public ModelAndView handleINotFoundException(HttpServletRequest request, NotFoundException ex) { public ModelAndView handleINotFoundException(HttpServletRequest request, NotFoundException ex) {
return new ModelAndView("redirect:/"); return new ModelAndView(REDIRECT_HOME);
} }
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class) @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
public ModelAndView handleMethodArgumentTypeMismatchException(HttpServletRequest request, MethodArgumentTypeMismatchException ex) { public ModelAndView handleMethodArgumentTypeMismatchException(HttpServletRequest request, MethodArgumentTypeMismatchException ex) {
return new ModelAndView("redirect:/"); return new ModelAndView(REDIRECT_HOME);
} }
@ExceptionHandler(value = UserNotLoginException.class) @ExceptionHandler(value = UserNotLoginException.class)
public ModelAndView handleUserNotLoginException(HttpServletRequest request, UserNotLoginException ex) { public ModelAndView handleUserNotLoginException(HttpServletRequest request, UserNotLoginException ex) {
return new ModelAndView("redirect:/login?reason=auth"); return new ModelAndView(REDIRECT_LOGIN_AUTH);
} }
@ExceptionHandler(value = AccessDeniedException.class) @ExceptionHandler(value = AccessDeniedException.class)
@@ -61,7 +66,7 @@ public class PortalGlobalExceptionHandler {
if (!SecurityUtil.isAuthenticated()) { if (!SecurityUtil.isAuthenticated()) {
// 원래 요청 페이지를 세션에 저장 → 로그인+2FA 완료 후 LoginFinalizer 가 복귀시킨다. // 원래 요청 페이지를 세션에 저장 → 로그인+2FA 완료 후 LoginFinalizer 가 복귀시킨다.
savePostLoginRedirect(request); savePostLoginRedirect(request);
return new ModelAndView("redirect:/login?reason=auth"); return new ModelAndView(REDIRECT_LOGIN_AUTH);
} }
log.warn("접근 권한 없음: loginId={}, uri={}", StringMaskingUtil.maskLoginId(SecurityUtil.getCurrentLoginId()), request.getRequestURI()); log.warn("접근 권한 없음: loginId={}, uri={}", StringMaskingUtil.maskLoginId(SecurityUtil.getCurrentLoginId()), request.getRequestURI());
ModelAndView modelAndView = new ModelAndView("error"); ModelAndView modelAndView = new ModelAndView("error");
@@ -122,9 +127,40 @@ public class PortalGlobalExceptionHandler {
|| path.startsWith("/favicon")); || path.startsWith("/favicon"));
} }
/**
* 리다이렉트/뷰 이름으로 허용할 문자. 스킴 구분자(:)·중괄호·달러를 막아
* 외부 URL 이나 Thymeleaf 표현식이 뷰 이름으로 흘러드는 것을 차단한다.
*/
private static final Pattern SAFE_REDIRECT_TARGET = Pattern.compile("[A-Za-z0-9._/?=&%-]+");
@ExceptionHandler(value = PortalRedirectException.class) @ExceptionHandler(value = PortalRedirectException.class)
public ModelAndView handlePortalRedirectException(HttpServletRequest request, PortalRedirectException ex) { public ModelAndView handlePortalRedirectException(HttpServletRequest request, PortalRedirectException ex) {
return new ModelAndView(ex.getMessage()); // PortalRedirectException 은 super(message) 를 호출하지 않으므로 getMessage() 는 항상 null 이다.
// 이전 구현은 그 null 을 뷰 이름으로 넘겨(= 뷰 미지정) 요청 URL 기준으로 뷰가 추론되게 만들었다.
// 실제 대상은 redirectPage 필드다.
String target = ex.getRedirectPage();
if (!isSafeRedirectTarget(target)) {
log.warn("허용되지 않은 리다이렉트 대상 - uri={}, target={}", request.getRequestURI(), target);
return new ModelAndView(REDIRECT_HOME);
}
return new ModelAndView(target);
}
/** 내부 경로/뷰 이름만 허용(외부 URL·프로토콜 상대 URL·표현식 문자 차단) */
private boolean isSafeRedirectTarget(String target) {
if (target == null) {
return false;
}
String path = target;
if (path.startsWith(REDIRECT_PREFIX)) {
path = path.substring(REDIRECT_PREFIX.length());
} else if (path.startsWith("forward:")) {
path = path.substring("forward:".length());
}
if (path.isEmpty() || path.startsWith("//")) { // //evil.example 형태의 외부 리다이렉트 차단
return false;
}
return SAFE_REDIRECT_TARGET.matcher(path).matches();
} }
@ExceptionHandler(value = {IllegalArgumentException.class}) @ExceptionHandler(value = {IllegalArgumentException.class})
@@ -140,7 +176,7 @@ public class PortalGlobalExceptionHandler {
public ModelAndView handleHttpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) { public ModelAndView handleHttpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
log.error(ex.getMessage()); log.error(ex.getMessage());
ModelAndView modelAndView = new ModelAndView(); ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:/"); modelAndView.setViewName(REDIRECT_HOME);
return modelAndView; return modelAndView;
} }
@@ -184,7 +220,7 @@ public class PortalGlobalExceptionHandler {
public ModelAndView handleInvalidFileException(HttpServletRequest request, RedirectAttributes redirectAttributes, InvalidFileException ex) { public ModelAndView handleInvalidFileException(HttpServletRequest request, RedirectAttributes redirectAttributes, InvalidFileException ex) {
ModelAndView modelAndView = new ModelAndView(); ModelAndView modelAndView = new ModelAndView();
redirectAttributes.addFlashAttribute("error", ex.getMessage()); redirectAttributes.addFlashAttribute("error", ex.getMessage());
modelAndView.setViewName("redirect:" + request.getRequestURI()); modelAndView.setViewName(REDIRECT_PREFIX + request.getRequestURI());
return modelAndView; return modelAndView;
} }
@@ -195,7 +231,7 @@ public class PortalGlobalExceptionHandler {
String errorMessage = "파일 크기가 허용된 최대 용량(" + maxSize + ")을 초과했습니다."; String errorMessage = "파일 크기가 허용된 최대 용량(" + maxSize + ")을 초과했습니다.";
redirectAttributes.addFlashAttribute("error", errorMessage); redirectAttributes.addFlashAttribute("error", errorMessage);
ModelAndView modelAndView = new ModelAndView(); ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("redirect:" + request.getRequestURI()); modelAndView.setViewName(REDIRECT_PREFIX + request.getRequestURI());
return modelAndView; return modelAndView;
} }
} }
@@ -1,6 +1,7 @@
package com.eactive.apim.portal.common.validator; package com.eactive.apim.portal.common.validator;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidator;
@@ -39,14 +40,8 @@ public class CellPhoneValidator implements ConstraintValidator<CellPhone, String
} }
// 기존의 prefix 체크 로직 유지 // 기존의 prefix 체크 로직 유지
return StringUtils.startsWith(cellPhone, "+821") || return Strings.CS.startsWithAny(cellPhone,
StringUtils.startsWith(cellPhone, "821") || "+821", "821", "010", "011", "016", "017", "018", "019");
StringUtils.startsWith(cellPhone, "010") ||
StringUtils.startsWith(cellPhone, "011") ||
StringUtils.startsWith(cellPhone, "016") ||
StringUtils.startsWith(cellPhone, "017") ||
StringUtils.startsWith(cellPhone, "018") ||
StringUtils.startsWith(cellPhone, "019");
} }
private boolean isValidPartLength(String prefix, String middle, String last) { private boolean isValidPartLength(String prefix, String middle, String last) {
@@ -2,6 +2,8 @@ package com.eactive.apim.portal.config;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.multipart.commons.CommonsMultipartResolver;
@@ -18,6 +20,18 @@ import org.springframework.web.multipart.commons.CommonsMultipartResolver;
@RequiredArgsConstructor @RequiredArgsConstructor
public class MultipartConfig { public class MultipartConfig {
/**
* 멀티파트 파트 1개의 헤더 구간 최대 바이트.
* <p>
* commons-fileupload 1.6.0(CVE-2025-48976 수정)부터 이 상한이 생겼고 기본값이 512 바이트다.
* 한글 파일명은 UTF-8 로 3바이트/자라 Content-Disposition 이 금방 커진다 — 실측상 기본값 512 로는
* 한글 약 137자 이상 파일명에서 업로드가 실패하고, 예외도 "Maximum upload size exceeded" 로 감싸져
* 원인 파악이 어렵다. OS 파일명 상한(255자)이 전부 한글이어도 통과하도록 2048 로 둔다.
* (1.5 이전에는 사실상 10240 이었으므로 이 값도 그보다 5배 엄격하다.)
* </p>
*/
private static final int PART_HEADER_SIZE_MAX = 2048;
private final PortalProperties portalProperties; private final PortalProperties portalProperties;
/** /**
@@ -31,7 +45,14 @@ public class MultipartConfig {
*/ */
@Bean(name = "filterMultipartResolver") @Bean(name = "filterMultipartResolver")
public CommonsMultipartResolver filterMultipartResolver() { public CommonsMultipartResolver filterMultipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver(); CommonsMultipartResolver resolver = new CommonsMultipartResolver() {
@Override
protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
FileUpload fileUpload = super.newFileUpload(fileItemFactory);
fileUpload.setPartHeaderSizeMax(PART_HEADER_SIZE_MAX);
return fileUpload;
}
};
long maxSizeBytes = portalProperties.getFile().getMaxSizeBytes(); long maxSizeBytes = portalProperties.getFile().getMaxSizeBytes();
resolver.setMaxUploadSize(maxSizeBytes); resolver.setMaxUploadSize(maxSizeBytes);
@@ -39,8 +60,8 @@ public class MultipartConfig {
resolver.setMaxInMemorySize((int) Math.min(maxSizeBytes, Integer.MAX_VALUE)); resolver.setMaxInMemorySize((int) Math.min(maxSizeBytes, Integer.MAX_VALUE));
resolver.setDefaultEncoding("UTF-8"); resolver.setDefaultEncoding("UTF-8");
log.info("CommonsMultipartResolver configured with maxUploadSize: {} bytes ({})", log.info("CommonsMultipartResolver configured with maxUploadSize: {} bytes ({}), partHeaderSizeMax: {} bytes",
maxSizeBytes, portalProperties.getFile().getMaxSize()); maxSizeBytes, portalProperties.getFile().getMaxSize(), PART_HEADER_SIZE_MAX);
return resolver; return resolver;
} }
@@ -7,6 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -16,6 +17,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfException; import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.session.HttpSessionEventPublisher; import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -92,6 +94,23 @@ public class PortalConfigSecurity {
http http
.authenticationManager(portalAuthenticationManager) .authenticationManager(portalAuthenticationManager)
// CVE-2026-22732 우회책. Spring Security 는 기본적으로 보안 헤더를 "응답 커밋 시점"에
// 지연 기록하는데, 응답 래퍼가 Content-Length 를 setHeader/setIntHeader/addIntHeader 로
// 지정하는 경로를 추적하지 못해 그 경우 헤더가 통째로 누락된다
// (X-Content-Type-Options, X-Frame-Options, Cache-Control, Pragma, Expires, X-XSS-Protection).
// 수정본은 5.7.22/5.8.24(Enterprise 전용)뿐이라 OSS 로는 올릴 수 없어 우회책을 적용한다.
// 요청 시작 시점에 헤더를 기록하게 만든다. 앱이 나중에 같은 헤더를 지정하면 앱 값이 남는다
// (실측: FileDownloadController#viewImage 의 Cache-Control: public, max-age=86400 유지됨).
// 현재 앱은 response.setContentLength(int) 만 쓰므로 노출 경로는 없지만,
// 새 코드가 위 메서드를 쓰더라도 헤더가 빠지지 않도록 두는 안전망이다.
// Spring Security 를 수정본(6.5.9+/7.0.4+ 또는 Enterprise 5.7.22+)으로 올리면 제거 가능.
.headers(headers -> headers.addObjectPostProcessor(new ObjectPostProcessor<HeaderWriterFilter>() {
@Override
public <O extends HeaderWriterFilter> O postProcess(O filter) {
filter.setShouldWriteHeadersEagerly(true);
return filter;
}
}))
.formLogin(form -> form .formLogin(form -> form
.loginPage("/login") .loginPage("/login")
.usernameParameter("id") .usernameParameter("id")
-491
View File
@@ -1,491 +0,0 @@
class ChartEditor {
constructor(container, layoutManager, dashboard) {
this.rootContainer = container;
this.layoutManager = layoutManager;
this.dashboard = dashboard;
this.targetContainerIndex = -1;
this.targetColumnIndex = -1;
this.tempChart = null;
this.chartPreview = null;
this.containerHeight = 0;
this.chartEditorModel = [
{
'groupName': 'default',
'groupTitle': '기본 정보',
'fields': [
{
'path': 'contentId',
'type': 'text',
'label': 'Chart ID',
'placeholder': 'chartId를 입력하세요'
},
{
'path': 'styles.width',
'type': 'text',
'label': '길이(px, %)',
'placeholder': 'px, % 등 단위를 포함한 길이를 입력하세요'
},
{
'path': 'styles.height',
'type': 'text',
'label': '높이(px, %)',
'placeholder': 'px, % 등 단위를 포함한 높이를 입력하세요'
},
{
'path': 'config.animation',
'type': 'boolean',
'label': 'Animation 사용'
}
]
},
{
'groupName': 'title',
'groupTitle': '제목',
'fields': [
{
'path': 'config.title.text',
'type': 'text',
'label': '차트 제목',
'placeholder': '차트 제목을 입력하세요'
}
]
},
{
'groupName': 'tooltip',
'groupTitle': 'Tooltip',
'fields': [
{
'path': 'config.tooltip.show',
'type': 'boolean',
'label': 'Tooltip 표시'
},
{
'path': 'config.tooltip.trigger',
'type': 'select',
'label': 'Tooltip Trigger',
'placeholder': 'Enter the trigger type',
'options': ['item', 'axis', 'none']
}
]
},
{
'groupName': 'legend',
'groupTitle': 'Legend',
'fields': [
{
'path': 'config.legend.show',
'type': 'boolean',
'label': 'Legend 표시'
}
]
},
{
'groupName': 'grid',
'groupTitle': 'Grid',
'fields': [
{
'path': 'config.grid.show',
'type': 'boolean',
'label': 'Grid 표시'
},
{
'path': 'config.grid.left',
'type': 'text',
'label': 'Grid Left',
'placeholder': '좌측 여백'
},
{
'path': 'config.grid.right',
'type': 'text',
'label': 'Grid Right',
'placeholder': '우측 여백'
},
{
'path': 'config.grid.top',
'type': 'text',
'label': 'Grid Top',
'placeholder': '위쪽 여백'
},
{
'path': 'config.grid.bottom',
'type': 'text',
'label': 'Grid Bottom',
'placeholder': '아래쪽 여백'
},
{
'path': 'config.grid.containLabel',
'type': 'boolean',
'label': 'Contain Label'
}
]
},
{
'groupName': 'xAxis',
'groupTitle': 'X축',
'fields': [
{
'path': 'config.xAxis.show',
'type': 'boolean',
'label': 'X축 표시'
},
{
'path': 'config.xAxis.type',
'type': 'select',
'label': 'X축 유형',
'placeholder': 'X축 유형을 선택하세요',
'options': ['category', 'value', 'time']
}
]
},
{
'groupName': 'yAxis',
'groupTitle': 'Y축',
'fields': [
{
'path': 'config.yAxis[0].show',
'type': 'boolean',
'label': 'Y축 표시'
},
{
'path': 'config.yAxis[0].name',
'type': 'text',
'label': 'Y축 이름'
},
{
'path': 'config.yAxis[0].type',
'type': 'select',
'label': 'Y축 유형',
'placeholder': 'Y축 유형을 선택하세요',
'options': ['category', 'value', 'time']
},
{
'path': 'config.yAxis[0].position',
'type': 'select',
'label': 'Y축 위치',
'placeholder': 'Y축 위치를 선택하세요',
'options': ['left', 'right']
}
]
},
{
'groupName': 'toolbox',
'groupTitle': 'Toolbox',
'fields': [
{
'path': 'config.toolbox.show',
'type': 'boolean',
'label': 'Toolbox 표시',
'placeholder': 'Check to show toolbox'
},
{
'path': 'config.toolbox.feature.saveAsImage.show',
'type': 'boolean',
'label': 'Save as Image 사용'
}
]
}
];
this.seriesConfigModel = [
{
'groupName': 'default',
'groupTitle': '시리즈 정보',
'fields': [
{
'path': 'seriesConfig[0].queryType',
'type': 'select',
'label': 'Query Type',
'options': ['elasticsearch']
},
// {
// 'path': 'seriesConfig[0].type',
// 'type': 'select',
// 'label': '차트 유형',
// 'placeholder': '차트 유형을 선택하세요',
// 'options': ['bar', 'line', 'pie']
// },
{
'path': 'seriesConfig[0].index',
'type': 'text',
'label': 'Elasticsearch Index',
'placeholder': 'Elasticsearch Index를 입력하세요'
},
{
'path': 'seriesConfig[0].query',
'type': 'textarea',
'label': 'Elasticsearch Query'
},
{
'path': 'seriesConfig[0].queryResult',
'type': 'textarea',
'label': 'Elasticsearch Query Result'
},
{
'path': 'seriesConfig[0].dataPath',
'type': 'text',
'label': '데이터 경로',
'placeholder': '데이터 경로를 입력하세요'
}
]
}
];
}
initChartEditor() {
const chartEditorPopup = `
<div class="modal fade" id="chart_editor_modal" tabindex="-1" aria-labelledby="chart_editor_modal" aria-hidden="true">
<div class="modal-dialog modal-fullscreen modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">차트 정보</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="editor_container">
<input type="hidden" name="type" value="" />
<div class="left-panel default-info">
${LayoutUtil.createForm(this.chartEditorModel)}
</div>
<div class="editor-right-panel">
<div class="series-config d-flex flex-column">
<h6>Data Source</h6>
<div class="d-flex flex-row">
<div class="form-floating me-2" style="flex: 1">
<select class="form-select" name="seriesConfig[0].queryType">
<option value="elasticsearch">Elasticsearch</option>
</select>
<label for="queryType">Query Type</label>
</div>
<div class="form-floating me-2" style="flex: 1">
<input class="form-control" type="text" name="seriesConfig[0].index" value="" />
<label for="index">Elasticsearch Index</label>
</div>
</div>
<div class="d-flex flex-row mt-2" style="flex: 1">
<div style="flex: 1">
<div class="form-floating me-2" style="height: 100%">
<textarea class="form-control" type="text" name="seriesConfig[0].query" id="seriesConfigQuery" placeholder="Code goes here..." style="height: 400px;">
</textarea>
</div>
</div>
<div style="flex: 1">
<div class="form-floating me-2" style="height: 100%">
<textarea class="form-control" type="text" name="seriesConfig[0].queryResult" style="height: 400px;" readonly>
</textarea>
<label for="query">요청 결과</label>
</div>
</div>
</div>
<div class="d-flex flex-row mt-2" style="flex: 1">
<div style="flex: 1">
<div class="form-floating me-2" style="height: 100%">
<textarea class="form-control" type="text" name="seriesConfig[0].mapperY" style="height: 400px;">
</textarea>
<label for="mapper">데이터 Mapper</label>
</div>
</div>
<div style="flex: 1">
<div class="form-floating me-2" style="height: 100%">
<textarea class="form-control" type="text" name="seriesConfig[0].mapperX" style="height: 400px;">
</textarea>
<label for="mapper">X축 Mapper</label>
</div>
</div>
</div>
<div class="d-flex flex-row" style="height: 400px">
<div style="flex: 1">
<div class="form-floating me-2" style="height: 100%">
<textarea class="form-control" type="text" id="echartOption" style="width: 100%; height: 400px;" readonly>
</textarea>
<label for="mapper">생성된 Option</label>
</div>
</div>
<div style="flex: 1">
<div class="chart-preview form-control" id="chart-preview" style="height: 400px;">
chart_preview
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary btn-sm add-chart">추가</button>
<button type="button" class="btn btn-primary btn-sm save-chart">저장</button>
</div>
</div>
</div>
</div>`;
$(this.rootContainer).parent().append(chartEditorPopup);
//add event to modal show
$('#chart_editor_modal').on('shown.bs.modal', (event) => {
let chartContainer = document.getElementById('chart-preview');
let rect = chartContainer.getBoundingClientRect();
let width = rect.width; // Precise width
let height = rect.height; // Precise height
this.chartPreview = echarts.init(document.getElementById('chart-preview'), 'dark', {
width: width, height: height
});
this.drawCharts();
});
}
showChartEditor(isNew, tempChart, containerIndex, columnIndex) {
this.targetContainerIndex = containerIndex;
this.targetColumnIndex = columnIndex;
this.tempChart = tempChart;
if (isNew) {
$('.add-chart').show();
$('.save-chart').hide();
} else {
$('.add-chart').hide();
$('.save-chart').show();
}
let flatModel = LayoutUtil.flattenObject(tempChart);
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
if ($(element).attr('type') === 'checkbox') {
$(element).prop('checked', false);
}
$(element).val('');
});
_.forEach(flatModel, (value, key) => {
const inputSelector = `input[name="${key}"]`;
const selectSelector = `select[name="${key}"]`;
const textareaSelector = `textarea[name="${key}"]`;
const $input = $('#chart_editor_modal').find(inputSelector);
const $select = $('#chart_editor_modal').find(selectSelector);
const $textarea = $('#chart_editor_modal').find(textareaSelector);
if ($input.length > 0 && $input.attr('type') === 'checkbox') {
// If it's a checkbox, update its 'checked' property
$input.prop('checked', value);
} else if ($input.length > 0 || $select.length > 0 || $textarea.length > 0) {
// If any input, select, or textarea is found, update its value
$($input.add($select).add($textarea)).val(value);
} else {
// If no elements are found, log to the console
console.log(`Element not found for name: ${key}`);
}
});
$('#chart_editor_modal').modal('show');
console.log('chart_editor_modal show');
this.bindEvents();
}
addChart() {
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
const fieldName = $(element).attr('name');
const newValue = LayoutUtil.getFieldValue(element);
if (fieldName !== '') {
_.set(this.tempChart, fieldName, newValue);
}
});
this.layoutManager.layoutData.children[this.targetContainerIndex].children.push(JSON.parse(JSON.stringify(this.tempChart)));
this.tempChart = null;
this.targetContainerIndex = -1;
this.targetColumnIndex = -1;
this.layoutManager.resetLayout();
$('#chart_editor_modal').modal('hide');
}
cleanUpEvents() {
console.log('cleanUpEvents');
$('.add-chart').off('click');
$('.save-chart').off('click');
}
saveChart() {
console.log('saveChart');
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
const fieldName = $(element).attr('name');
const newValue = LayoutUtil.getFieldValue(element);
if (fieldName !== undefined && fieldName !== '') {
_.set(this.tempChart, fieldName, newValue);
}
});
const json = JSON.stringify(this.tempChart, LayoutUtil.echartReplacer, 2);
console.log(json);
this.layoutManager.layoutData.children[this.targetContainerIndex].children[this.targetColumnIndex] = JSON.parse(json);
this.targetContainerIndex = -1;
this.targetColumnIndex = -1;
this.layoutManager.resetLayout();
$('#chart_editor_modal').modal('hide');
}
refreshChart(chartInfo) {
console.log('refreshChart');
return new Promise((resolve, reject) => {
let option = JSON.parse(JSON.stringify(chartInfo.config));
option.series = [];
let self = this;
Promise.all(this.tempChart.seriesConfig.map(config => this.dashboard.requestQuery(config))).then(async (results) => {
await self.dashboard.updateChartSeries(option, results, chartInfo, (index, result) => {
$('#chart_editor_modal').find(`textarea[name='seriesConfig[${index}].queryResult']`).val('result = ' + JSON.stringify(result, null, 2));
});
document.getElementById('echartOption').value = JSON.stringify(option, null, 2);
self.chartPreview.setOption(option);
resolve();
}).catch((error) => {
console.error('Error fetching data for preview: ', error);
reject(error);
});
});
}
drawCharts() {
console.log('drawCharts');
const promises = [this.refreshChart(this.tempChart)];
Promise.all(promises).then(() => console.log('Chart Preview is completed')).catch(error => console.error('An error occurred:', error));
}
bindEvents() {
$('.add-chart').off('click').on('click', (event) => {
this.addChart();
});
$('.save-chart').off('click').on('click', (event) => {
this.saveChart();
});
$('#chart_editor_modal').find('input, select, textarea').each((index, element) => {
$(element).off('change').on('change', (event) => {
console.log('change event');
const fieldName = $(event.currentTarget).attr('name');
const newValue = LayoutUtil.getFieldValue(event.currentTarget);
if (fieldName !== '') {
_.set(this.tempChart, fieldName, newValue);
}
this.refreshChart(this.tempChart);
});
});
$('#chart_editor_modal').on('hidden.bs.modal', (event) => {
if (this.chartPreview != null && this.chartPreview.dispose) {
this.chartPreview.dispose();
this.chartPreview = null;
}
this.cleanUpEvents();
});
}
}
-344
View File
@@ -1,344 +0,0 @@
class Dashboard {
constructor(layoutData) {
this.layoutData = layoutData || {};
this.fromDateTarget = 'fromDate';
this.toDateTarget = 'toDate';
this.charts = [];
this.theme = 'dark';
this.requestUrl = '/mgmt/dashboard/search.do';
}
bindEvents() {
const events = [{selector: '.refresh', action: this.refresh.bind(this)}];
for (let event of events) {
$(document).off('click', event.selector).on('click', event.selector, event.action);
}
}
getDateTemplate(id, label) {
return `
<div class="input-group mb-3">
<div class="form-floating">
<input id="${id}Input" type="text" class="form-control" data-td-target="#${id}"/>
<label for="${id}Input">${label}</label>
</div>
<span class="input-group-text" data-td-target="#${id}" data-td-toggle="datetimepicker">
<span class="fa-solid fa-calendar"></span>
</span>
</div>
`;
}
init(chartAreaId) {
console.log('Dashboard init');
this.chartAreaId = chartAreaId;
this.initDate();
this.initCharts();
this.drawCharts();
window.addEventListener('resize', this.resizeAllCharts.bind(this));
}
initCharts() {
console.log('initCharts');
this.charts = [];
if (this.layoutData.children) {
this.layoutData.children.forEach((row) => {
row.children.forEach((chartInfo) => {
this.charts.push(chartInfo);
});
});
}
}
formatDate(isoString) {
const date = new Date(isoString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // +1 because getMonth() returns 0-11
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
formatTime(isoString) {
const date = new Date(isoString);
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
return `${hour}:${minute}:00`;
}
formatDateTime(isoString) {
return this.formatDate(isoString) + 'T' + this.formatTime(isoString);
}
startOfDay(isoString) {
return this.formatDate(isoString) + 'T00:00:00';
}
initDate() {
const ko = tempusDominus.locales.ko.localization;
const fromDateElement = document.getElementById(this.fromDateTarget);
$(fromDateElement).data('td-target-input', 'nearest');
$(fromDateElement).data('td-target-toggle', 'nearest');
const toDateElement = document.getElementById(this.toDateTarget);
$(toDateElement).data('td-target-input', 'nearest');
$(toDateElement).data('td-target-toggle', 'nearest');
$(fromDateElement).append(this.getDateTemplate(this.fromDateTarget, 'From'));
$(toDateElement).append(this.getDateTemplate(this.toDateTarget, 'To'));
this.fromDate = new tempusDominus.TempusDominus(fromDateElement, {
localization: ko, defaultDate: this.startOfDay(new Date())
});
this.toDate = new tempusDominus.TempusDominus(toDateElement, {
useCurrent: false, localization: ko, defaultDate: new Date()
});
fromDateElement.addEventListener(tempusDominus.Namespace.events.change, (e) => {
this.toDate.updateOptions({
restrictions: {
minDate: e.detail.date
}
});
});
const subscription = this.toDate.subscribe(tempusDominus.Namespace.events.change, (e) => {
this.fromDate.updateOptions({
restrictions: {
maxDate: e.date
}
});
});
}
requestPost(url, data, hds) {
var deferred = $.Deferred();
var headers = $.extend({
'Content-Type': 'application/json; charset=utf-8'
}, hds);
if (headers['Content-Type'].startsWith('application/json')) {
data = JSON.stringify(data);
}
$.ajax({
url: url, async: true, method: 'POST', data: data, headers: headers
}).done((response) => {
deferred.resolve(response);
}).fail((response, textStatus, errorThrown) => {
deferred.reject(response, textStatus, errorThrown);
});
return deferred.promise();
};
requestQuery(seriesConfig) {
let request = null;
try {
if (seriesConfig.queryType === 'elasticsearch') {
if (seriesConfig.query && seriesConfig.index !== '') {
let requestQuery = JSON.parse(seriesConfig.query);
requestQuery.query = requestQuery.query || {};
requestQuery.query.bool = requestQuery.query.bool || {};
requestQuery.query.bool.filter = requestQuery.query.bool.filter || [];
requestQuery.query.bool.filter.push({
'range': {
'@timestamp': {
'gte': this.formatDateTime(this.fromDate.dates.picked[0]),
'lte': this.formatDateTime(this.toDate.dates.picked[0]),
'time_zone': 'Asia/Seoul'
}
}
});
request = {
query: JSON.stringify(requestQuery), index: seriesConfig.index
};
return this.requestPost(this.requestUrl, request, {});
} else {
console.log('query is empty');
return Promise.resolve();
}
}
} catch (error) {
console.error('Error fetching data for ' + seriesConfig.contentId + ': ', error);
return Promise.reject(error);
}
}
resizeAllCharts() {
this.charts.forEach(chartInfo => {
let chartContainer = document.getElementById('chart_' + chartInfo.contentId);
let rect = chartContainer.getBoundingClientRect();
let width = rect.width; // Precise width
let height = rect.height; // Precise height
if (chartInfo.echart) {
chartInfo.echart.resize({width: width, height: height});
}
});
}
initializeChart(chartInfo) {
console.log('initializeChart: ' + chartInfo.contentId);
let chartContainer = document.getElementById('chart_' + chartInfo.contentId);
let rect = chartContainer.getBoundingClientRect();
let width = rect.width; // Precise width
let height = rect.height; // Precise height
chartInfo.echart = echarts.init(document.getElementById('chart_' + chartInfo.contentId), this.theme, {
width: width, height: height
});
}
executeScript(result, script) {
console.log('executeScript');
if (script) {
return new Promise((resolve, reject) => {
let resultFrame = document.getElementById('mapperScript');
window.preRequestComplete = (result, error) => {
if (error) {
console.log('Error executing script: ', error);
reject(error);
} else {
resolve(result);
}
};
window.result = result;
resultFrame.srcdoc = `<script>
function get(obj, path, defaultValue = undefined) {
const keys = Array.isArray(path) ? path : path.replace(/\\[(\\d+)\\]/g, '.$1').split('.');
let result = obj;
for (let key of keys) {
result = result?.[key];
if (result === undefined) {
return defaultValue;
}
}
return result;
}
function formatDate(isoString) {
const date = new Date(isoString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // +1 because getMonth() returns 0-11
const day = String(date.getDate()).padStart(2, '0');
return \`\${year}-\${month}-\${day}\`;
}
function formatTime(isoString) {
const date = new Date(isoString);
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
return \`\${hour}:\${minute}:00\`;
}
function formatDateTime(isoString) {
return formatDate(isoString) + 'T' + formatTime(isoString);
}
function startOfDay(isoString) {
return formatDate(isoString) + 'T00:00:00';
}
window.result = window.parent.result;
try {
const executeFunction = ${script};
const data = executeFunction(window.result);
window.parent.preRequestComplete(data, null);
} catch (error) {
window.parent.preRequestComplete(null, error);
}
</script>`;
});
} else {
console.log('No script to execute');
return Promise.resolve();
}
}
async refresh(event) {
console.log('refresh');
for (let i = 0; i < this.charts.length; i++) {
const chartInfo = this.charts[i];
try {
await this.refreshChart(chartInfo);
} catch (error) {
console.error(`Error refreshing chart ${chartInfo.contentId}:`, error);
}
}
console.log('All Charts requests are complete');
// const promises = this.charts.map(info => this.refreshChart(info));
// Promise.all(promises).then(() => console.log('All Charts requests are complete')).catch(error => console.error('An error occurred:', error));
}
drawCharts() {
console.log('drawCharts');
this.charts.forEach(info => this.initializeChart(info));
this.refresh();
this.bindEvents();
}
async updateChartSeries(option, results, chartInfo, callback) {
console.log('updateChartSeries');
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result){
let config = chartInfo.seriesConfig[i];
let parsedResult = JSON.parse(result);
if (callback) {
callback(i, parsedResult);
}
try {
console.log('start executeScript');
const dataY = await this.executeScript(parsedResult, config.mapperY);
if (Array.isArray(dataY)) {
dataY.forEach(item => {
option.series.push(item);
});
} else {
option.series.push(dataY);
}
const dataX = await this.executeScript(parsedResult, config.mapperX);
option.xAxis.data = dataX;
console.log('stop executeScript');
} catch (error) {
console.error(`Error updating series for chart ${chartInfo.contentId}:`, error);
}
}
}
}
async refreshChart(chartInfo) {
try {
console.log('refreshChart');
let option = JSON.parse(JSON.stringify(chartInfo.config));
option.series = [];
const results = await Promise.all(chartInfo.seriesConfig.map(config => this.requestQuery(config)));
await this.updateChartSeries(option, results, chartInfo);
// Clear the chart and set the new option
chartInfo.echart.clear();
chartInfo.echart.setOption(option);
console.log('Chart refreshed successfully');
} catch (error) {
console.error('Error fetching data for ' + chartInfo.contentId + ': ', error);
throw error; // Rethrow the error if you need to catch it higher up in the call stack
}
}
}
@@ -1,643 +0,0 @@
class LayoutManager {
constructor(container, layoutData) {
this.rootContainer = container;
this.tempContainer = null;
this.editContainerIndex = -1;
this.layoutData = layoutData || {
type: 'root',
styles: {
'display': 'flex',
'flex-direction': 'column',
'justify-content': 'start',
'align-items': 'flex-start',
'gap': '10px',
'width': '100%',
'height': '100vh',
'position': 'relative',
'overflow': 'auto',
'background-color': '#100C2A'
},
children: []
};
this.containerEditorModel = [
{
'groupName': 'default',
'groupTitle': '기본',
'fields': [
{
'path': 'styles.display',
'type': 'select',
'label': 'Display',
'options': ['flex']
},
{
'path': 'styles.flex-direction',
'type': 'select',
'label': 'Flex 방향',
'options': ['row', 'column', 'row-reverse', 'column-reverse']
},
{
'path': 'styles.width',
'type': 'text',
'label': '길이(px, %)',
'placeholder': 'px, % 등 단위를 포함한 길이를 입력하세요'
},
{
'path': 'styles.height',
'type': 'text',
'label': '높이(px, %)',
'placeholder': 'px, % 등 단위를 포함한 높이를 입력하세요'
}
]
}
];
this.dashboardEditorModel = [
{
'groupName': 'default',
'groupTitle': '기본',
'fields': [
{
'path': 'styles.background-color',
'type': 'text',
'label': '색상',
'placeholder': '#로 시작하는 값을 입력하세요'
},
{
'path': 'styles.width',
'type': 'text',
'label': '길이(px)',
'placeholder': 'px 단위를 포함한 길이를 입력하세요'
},
{
'path': 'styles.height',
'type': 'text',
'label': '높이(px)',
'placeholder': 'px 단위를 포함한 높이를 입력하세요'
}
]
}
]
}
// traverse(obj) {
// if (obj.type === 'chart') {
// $(`#chart_${obj.contentId}`).closest('.dashboard-chart').resizable({
// grid: 10,
// ghost: true,
// stop: (event, ui) => {
// const {width, height} = ui.size;
// obj.styles.width = `${width}px`;
// obj.styles.height = `${height}px`;
// this.resetLayout();
// }
// });
// }
//
// if (obj.children) {
// obj.children.forEach(child => {
// this.traverse(child);
// });
// }
// }
init(Dashboard, viewerMode) {
this.viewMode = viewerMode || false;
this.renderRoot(this.layoutData, this.rootContainer);
if (Dashboard) {
this.dashboard = new Dashboard(this.layoutData);
this.dashboard.init('chart_area');
}
if (!this.viewMode) {
this.initContainerEditor();
this.initDashboardEditor();
this.chartEditor = new ChartEditor(this.rootContainer, this, this.dashboard);
this.chartEditor.initChartEditor();
this.bindEvents();
}
}
initContainerEditor() {
const containerEditorPopup = `
<div class="modal fade" id="container_editor_modal" tabindex="-1" aria-labelledby="container_editor_modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">컨테이너 정보</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="containerOption">
${LayoutUtil.createForm(this.containerEditorModel)}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary btn-sm add-container">추가</button>
<button type="button" class="btn btn-primary btn-sm save-container">저장</button>
</div>
</div>
`;
$(this.rootContainer).parent().append(containerEditorPopup);
}
initDashboardEditor() {
const containerEditorPopup = `
<div class="modal fade" id="dashboard_editor_modal" tabindex="-1" aria-labelledby="dashboard_editor_modal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">대시보드 정보</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="containerOption">
${LayoutUtil.createForm(this.dashboardEditorModel)}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary btn-sm save-dashboard">적용</button>
</div>
</div>
`;
$(this.rootContainer).parent().append(containerEditorPopup);
}
resetLayout() {
console.log('resetLayout');
$(this.rootContainer).empty();
this.renderRoot(this.layoutData, this.rootContainer);
this.dashboard.initCharts();
this.dashboard.layoutData = this.layoutData;
this.dashboard.drawCharts();
this.editContainerIndex = -1;
this.bindEvents();
}
addNewChart(containerIndex) {
this.editContainerIndex = containerIndex;
let tempChart = {
'contentId': '',
'type': 'chart',
'styles': {
width: '100%', height: '100%'
},
'config': {
'tooltip': {
'trigger': 'axis',
'show': true
},
'legend': {
'show': true
},
'grid': {
'show': true,
'top': 0,
'bottom': 0,
'left': 0,
'right': 0,
'containLabel': true
},
'title': {},
'xAxis': {
'show': true,
'type': 'category'
},
'yAxis': [
{
'show': true,
'name': '',
'type': 'value',
'position': 'left'
}],
'toolbox': {
'show': true
}
},
'seriesConfig': [
{
'type': 'line',
'index': '',
'queryType': 'elasticsearch',
'query': '',
'mapperX': `
function(result) {
let data = [];
return data;
}
`,
'mapperY': `
function(result) {
let data = [{
type : 'line',
data: []
}];
return data;
}
`,
'queryResult': ''
}
]
};
this.chartEditor.showChartEditor(true, tempChart, containerIndex);
}
editChart(editContainerIndex, editColumnIndex) {
console.log('edit-chart');
let tempChart = this.layoutData.children[editContainerIndex].children[editColumnIndex];
this.chartEditor.showChartEditor(false, JSON.parse(JSON.stringify(tempChart, LayoutUtil.echartReplacer)), editContainerIndex, editColumnIndex);
}
renderRoot(layoutData, parent) {
let classList = 'dashboard-root mt-2' + (!this.viewMode ? ' edit' : '');
let children = '';
let style = '';
if (layoutData.styles) {
style = Object.keys(layoutData.styles).map(key => `${key}: ${layoutData.styles[key]};`).join(' ');
}
console.log({style});
if (layoutData.children) {
layoutData.children.forEach((child) => {
if (child.type === 'container') {
children += this.renderContainer(child);
} else if (child.type === 'chart') {
children += this.renderChart(child);
}
});
}
let layoutItem = `<div class="${classList}" style="${style}"> ${children}</div>`;
$(parent).append(layoutItem);
// if (!this.viewMode) {
// this.traverse(this.layoutData);
// }
};
renderChart(layoutData) {
let style = '';
if (layoutData.styles) {
style = Object.keys(layoutData.styles).map(key => `${key}: ${layoutData.styles[key]};`).join(' ');
}
let editButton = this.viewMode ? '' : `<div class="chart-menu-icon" data-content-id="chart_${layoutData.contentId}">
<div class="dropdown">
<span class="" data-bs-toggle="dropdown" aria-expanded="false">⋮</span>
<ul class="dropdown-menu">
<li><span class="dropdown-item edit-chart">차트 편집</span></li>
<li><span class="dropdown-item remove-chart">차트 제거</span></li>
</ul>
</div>
</div>`;
return `
<div class="dashboard-chart" data-content-id="chart_${layoutData.contentId}" style="${style}">
${editButton}
<div id="chart_${layoutData.contentId}" style="width: 100%; height: 100%"></div>
</div>
`;
}
renderContainer(layoutData) {
console.log('renderContainer');
let children = '';
if (layoutData.children) {
layoutData.children.forEach(child => {
if (child.type === 'container') {
children += this.renderContainer(child);
} else if (child.type === 'chart') {
children += this.renderChart(child);
}
});
}
let width = layoutData.styles.width || '100%';
let height = layoutData.styles.height || '100%';
let editButton = this.viewMode ? '' : `
<div class="container-menu-icon">
<div class="dropdown">
<span><i class="fa-solid fa-arrows-up-down-left-right"></i></span>
<span class="" data-bs-toggle="dropdown" aria-expanded="false">⋮</span>
<ul class="dropdown-menu">
<li><span class="dropdown-item edit-container">컨테이너 편집</span></li>
<li><span class="dropdown-item remove-container">컨테이너 제거</span></li>
<li><span class="dropdown-item add-new-chart">차트 추가</span></li>
</ul>
</div>
</div>`;
let style = '';
if (layoutData.styles) {
style = Object.keys(layoutData.styles).map(key =>{
if (key === 'width' || key === 'height') return `${key}: 100%;`;
return `${key}: ${layoutData.styles[key]};`;
} ).join(' ');
}
return `<div class="dashboard-container" style="width: ${width}; height: ${height};">
<div class="handle">
${editButton}
</div>
<div class="dashboard-container-children" style="${style}">
${children}
</div>
</div>`;
}
addNewContainer(event) {
this.tempContainer = {
type: 'container',
styles: {
display: 'flex',
'flex-direction': 'row',
width: '100%',
height: '300px'
},
children: []
};
this.showContainerEditor(true);
}
editContainer(editContainerIndex) {
console.log('edit-container: ' + editContainerIndex);
this.tempContainer = JSON.parse(JSON.stringify(this.layoutData.children[editContainerIndex], LayoutUtil.echartReplacer));
this.showContainerEditor(false);
this.editContainerIndex = editContainerIndex;
}
removeContainer(containerIndex) {
console.log('edit-container: ' + containerIndex);
this.layoutData.children.splice(containerIndex, 1);
this.resetLayout();
}
addContainer(event) {
$('#container_editor_modal').find('input, select, textarea').each((index, element) => {
const fieldName = $(element).attr('name');
const newValue = LayoutUtil.getFieldValue(element);
if (fieldName !== '') {
_.set(this.tempContainer, fieldName, newValue);
}
});
this.layoutData.children.push(JSON.parse(JSON.stringify(this.tempContainer)));
this.tempContainer = null;
this.resetLayout();
$('#container_editor_modal').modal('hide');
}
saveContainer(event) {
console.log('save-container: ' + this.editContainerIndex);
$('#container_editor_modal').find('input, select, textarea').each((index, element) => {
const fieldName = $(element).attr('name');
const newValue = LayoutUtil.getFieldValue(element);
if (fieldName !== '') {
_.set(this.tempContainer, fieldName, newValue);
}
});
this.layoutData.children[this.editContainerIndex] = JSON.parse(JSON.stringify(this.tempContainer));
this.tempContainer = null;
this.resetLayout();
$('#container_editor_modal').modal('hide');
}
removeChart(containerIndex, columnIndex) {
this.layoutData.children[containerIndex].children.splice(columnIndex, 1);
this.resetLayout();
}
onRowsSorted(ui) {
let originalContainerIndex = ui.item.data('original_row_index');
this.updateLayoutDataRows(originalContainerIndex, ui);
}
onColumnsSorted(ui) {
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
let originalContainerIndex = ui.item.data('original_row_index');
if (originalContainerIndex) {
if (originalContainerIndex === containerIndex) { // Check if the column moved from another row
this.updateLayoutDataWithinRow(ui.item);
} else { // The column was sorted within the same row
this.updateLayoutDataColumns(ui.item);
}
this.resetLayout();
}
}
updateLayoutDataRows(originalContainerIndex, ui) {
let targetIndex = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
if (targetIndex !== originalContainerIndex) {
this.layoutData.children.splice(targetIndex, 0, this.layoutData.children.splice(originalContainerIndex, 1)[0]);
}
console.log(this.layoutData);
}
updateLayoutDataWithinRow(movedItem) {
let oldContainerIndex = movedItem.data('original_row_index');
let oldIndex = movedItem.data('original_column_index');
if (oldContainerIndex !== -1 && oldIndex !== -1) {
let $row = movedItem.closest('.dashboard-container');
let newIndex = $row.children('.dashboard-chart').index(movedItem);
this.layoutData.children[oldContainerIndex].children.splice(newIndex, 0, this.layoutData.children[oldContainerIndex].children.splice(oldIndex, 1)[0]);
}
}
save() {
const jsonString = JSON.stringify(this.layoutData, LayoutUtil.echartReplacer);
return jsonString;
}
updateLayoutDataColumns(movedItem) {
// 새로운 행과 이전 행의 jQuery 요소를 가져옵니다
let $newRow = movedItem.closest('.dashboard-container');
// 이전 행의 레이아웃 데이터에서 이동된 행과 열의 인덱스를 찾습니다
let oldContainerIndex = movedItem.data('original_row_index');
let movedColumnIndex = movedItem.data('original_column_index');
if (oldContainerIndex !== -1 && movedColumnIndex !== undefined && movedColumnIndex !== -1) {
movedItem.data('original_row_index', -1);
movedItem.data('original_column_index', -1);
// 새 행의 인덱스를 찾습니다
let newContainerIndex = $('.dashboard-root').children('.dashboard-container').index($newRow);
// 이전 행에서 열 데이터를 추출합니다
let movedColumnData = this.layoutData.children[oldContainerIndex].children.splice(movedColumnIndex, 1)[0];
// 아이템이 드롭된 새 열 인덱스를 찾습니다
let newColumnIndex = movedItem.closest('.dashboard-container-children').children('.dashboard-chart').index(movedItem);
// 이동된 열 데이터를 새 행의 열에 새 인덱스에 삽입합니다
// 아이템이 마지막 위치로 이동된 경우 인덱스는 -1이 될 것이므로 이 경우를 처리합니다
if (newColumnIndex === -1) {
this.layoutData.children[newContainerIndex].children.push(movedColumnData);
} else {
this.layoutData.children[newContainerIndex].children.splice(newColumnIndex, 0, movedColumnData);
}
} else {
console.log('called twice on update');
}
}
showContainerEditor(isNew) {
if (isNew) {
$('.add-container').show();
$('.save-container').hide();
} else {
$('.add-container').hide();
$('.save-container').show();
}
let flatModel = LayoutUtil.flattenObject(JSON.parse(JSON.stringify(this.tempContainer)));
$('#container_editor_modal').find('input, select, textarea').each((index, element) => {
if ($(element).attr('type') === 'checkbox') {
$(element).prop('checked', false);
}
$(element).val('');
});
_.forEach(flatModel, (value, key) => {
if ($('#container_editor_modal').find(`input[name="${key}"]`).attr('type') === 'checkbox') {
$('#container_editor_modal').find(`input[name="${key}"]`).prop('checked', value);
} else {
$('#container_editor_modal').find(`input[name="${key}"], select[name="${key}"], textarea[name="${key}"]`).val(value);
}
});
$('#container_editor_modal').modal('show');
}
resize() {
console.log('resize');
this.dashboard.resizeAllCharts();
}
editDashboard(event) {
this.showDashboardEditor();
}
showDashboardEditor() {
let flatModel = LayoutUtil.flattenObject(JSON.parse(JSON.stringify(this.layoutData, LayoutUtil.echartReplacer)));
$('#dashboard_editor_modal').find('input, select, textarea').each((index, element) => {
if ($(element).attr('type') === 'checkbox') {
$(element).prop('checked', false);
}
$(element).val('');
});
_.forEach(flatModel, (value, key) => {
if ($('#dashboard_editor_modal').find(`input[name="${key}"]`).attr('type') === 'checkbox') {
$('#dashboard_editor_modal').find(`input[name="${key}"]`).prop('checked', value);
} else {
$('#dashboard_editor_modal').find(`input[name="${key}"], select[name="${key}"], textarea[name="${key}"]`).val(value);
}
});
$('#dashboard_editor_modal').modal('show');
}
saveDashboard(event) {
$('#dashboard_editor_modal').find('input, select, textarea').each((index, element) => {
const fieldName = $(element).attr('name');
const newValue = LayoutUtil.getFieldValue(element);
if (fieldName !== '') {
_.set(this.layoutData, fieldName, newValue);
}
});
console.log(this.layoutData);
this.resetLayout();
$('#dashboard_editor_modal').modal('hide');
}
bindEvents() {
$('.add-new-container').off('click').on('click', (event) => {
this.addNewContainer(event);
});
$('.edit-dashboard').off('click').on('click', (event) => {
this.editDashboard(event);
});
$('.save-dashboard').off('click').on('click', (event) => {
this.saveDashboard(event);
});
$('.remove-container').off('click').on('click', (event) => {
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
this.removeContainer(containerIndex);
});
$('.add-container').off('click').on('click', (event) => {
this.addContainer(event);
});
$('.save-container').off('click').on('click', (event) => {
this.saveContainer(event);
});
$('.edit-container').off('click').on('click', (event) => {
let editContainerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
this.editContainer(editContainerIndex);
});
$('.edit-chart').off('click').on('click', (event) => {
let editContainerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
let column = event.target.closest('.dashboard-chart');
let editColumnIndex = $(event.target.closest('.dashboard-container-children')).children('.dashboard-chart').index(column);
this.editChart(editContainerIndex, editColumnIndex);
});
$('.add-new-chart').off('click').on('click', (event) => {
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
this.addNewChart(containerIndex);
});
$('.remove-chart').off('click').on('click', (event) => {
let column = event.target.closest('.dashboard-chart');
let containerIndex = $('.dashboard-root').children('.dashboard-container').index(event.target.closest('.dashboard-container'));
let columnIndex = $(event.target.closest('.dashboard-container-children')).children('.dashboard-chart').index(column);
this.removeChart(containerIndex, columnIndex);
});
if ($('.dashboard-root').hasClass('ui-sortable')) {
$('.dashboard-root').sortable('destroy');
}
$('.dashboard-root').sortable({
handle: '.handle',
items: '.dashboard-container',
placeholder: 'ui-state-highlight',
start: (event, ui) => {
let originalRowIdx = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
ui.item.data('original_row_index', originalRowIdx);
}, update: (event, ui) => {
this.onRowsSorted(ui);
}
});
if ($('.dashboard-container').hasClass('ui-sortable')) {
$('.dashboard-container').sortable('destroy');
}
$('.dashboard-container-children').sortable({
// handle: ".handle",
placeholder: 'ui-state-highlight',
items: '.dashboard-chart',
connectWith: '.dashboard-container-children',
update: (event, ui) => {
this.onColumnsSorted(ui);
},
start: (event, ui) => {
let originalRowIdx = $('.dashboard-root').children('.dashboard-container').index(ui.item.closest('.dashboard-container'));
let originalColumnIdx = ui.item.closest('.dashboard-container-children').children('.dashboard-chart').index(ui.item);
ui.item.data('original_row_index', originalRowIdx);
ui.item.data('original_column_index', originalColumnIdx);
}
}).disableSelection();
}
}
File diff suppressed because one or more lines are too long
@@ -1,173 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports);
} else {
// Browser globals
factory({});
}
})(this, function(exports) {
/**
* Language: English.
*/
var localeObj = {
time: {
month: [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
],
monthAbbr: [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
],
dayOfWeek: [
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
],
dayOfWeekAbbr: [
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
]
},
legend: {
selector: {
all: 'All',
inverse: 'Inv'
}
},
toolbox: {
brush: {
title: {
rect: 'Box Select',
polygon: 'Lasso Select',
lineX: 'Horizontally Select',
lineY: 'Vertically Select',
keep: 'Keep Selections',
clear: 'Clear Selections'
}
},
dataView: {
title: 'Data View',
lang: ['Data View', 'Close', 'Refresh']
},
dataZoom: {
title: {
zoom: 'Zoom',
back: 'Zoom Reset'
}
},
magicType: {
title: {
line: 'Switch to Line Chart',
bar: 'Switch to Bar Chart',
stack: 'Stack',
tiled: 'Tile'
}
},
restore: {
title: 'Restore'
},
saveAsImage: {
title: 'Save as Image',
lang: ['Right Click to Save Image']
}
},
series: {
typeNames: {
pie: 'Pie chart',
bar: 'Bar chart',
line: 'Line chart',
scatter: 'Scatter plot',
effectScatter: 'Ripple scatter plot',
radar: 'Radar chart',
tree: 'Tree',
treemap: 'Treemap',
boxplot: 'Boxplot',
candlestick: 'Candlestick',
k: 'K line chart',
heatmap: 'Heat map',
map: 'Map',
parallel: 'Parallel coordinate map',
lines: 'Line graph',
graph: 'Relationship graph',
sankey: 'Sankey diagram',
funnel: 'Funnel chart',
gauge: 'Gauge',
pictorialBar: 'Pictorial bar',
themeRiver: 'Theme River Map',
sunburst: 'Sunburst'
}
},
aria: {
general: {
withTitle: 'This is a chart about "{title}"',
withoutTitle: 'This is a chart'
},
series: {
single: {
prefix: '',
withName: ' with type {seriesType} named {seriesName}.',
withoutName: ' with type {seriesType}.'
},
multiple: {
prefix: '. It consists of {seriesCount} series count.',
withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.',
withoutName: ' The {seriesId} series is a {seriesType}.',
separator: {
middle: '',
end: ''
}
}
},
data: {
allData: 'The data is as follows: ',
partialData: 'The first {displayCnt} items are: ',
withName: 'the data for {name} is {value}',
withoutName: '{value}',
separator: {
middle: ', ',
end: '. '
}
}
}
};
for (var key in localeObj) {
if (localeObj.hasOwnProperty(key)) {
exports[key] = localeObj[key];
}
}
});
@@ -1,169 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
/**
* Language: English.
*/
var localeObj = {
time: {
month: [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
],
monthAbbr: [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
],
dayOfWeek: [
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
],
dayOfWeekAbbr: [
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
]
},
legend: {
selector: {
all: 'All',
inverse: 'Inv'
}
},
toolbox: {
brush: {
title: {
rect: 'Box Select',
polygon: 'Lasso Select',
lineX: 'Horizontally Select',
lineY: 'Vertically Select',
keep: 'Keep Selections',
clear: 'Clear Selections'
}
},
dataView: {
title: 'Data View',
lang: ['Data View', 'Close', 'Refresh']
},
dataZoom: {
title: {
zoom: 'Zoom',
back: 'Zoom Reset'
}
},
magicType: {
title: {
line: 'Switch to Line Chart',
bar: 'Switch to Bar Chart',
stack: 'Stack',
tiled: 'Tile'
}
},
restore: {
title: 'Restore'
},
saveAsImage: {
title: 'Save as Image',
lang: ['Right Click to Save Image']
}
},
series: {
typeNames: {
pie: 'Pie chart',
bar: 'Bar chart',
line: 'Line chart',
scatter: 'Scatter plot',
effectScatter: 'Ripple scatter plot',
radar: 'Radar chart',
tree: 'Tree',
treemap: 'Treemap',
boxplot: 'Boxplot',
candlestick: 'Candlestick',
k: 'K line chart',
heatmap: 'Heat map',
map: 'Map',
parallel: 'Parallel coordinate map',
lines: 'Line graph',
graph: 'Relationship graph',
sankey: 'Sankey diagram',
funnel: 'Funnel chart',
gauge: 'Gauge',
pictorialBar: 'Pictorial bar',
themeRiver: 'Theme River Map',
sunburst: 'Sunburst'
}
},
aria: {
general: {
withTitle: 'This is a chart about "{title}"',
withoutTitle: 'This is a chart'
},
series: {
single: {
prefix: '',
withName: ' with type {seriesType} named {seriesName}.',
withoutName: ' with type {seriesType}.'
},
multiple: {
prefix: '. It consists of {seriesCount} series count.',
withName: ' The {seriesId} series is a {seriesType} representing {seriesName}.',
withoutName: ' The {seriesId} series is a {seriesType}.',
separator: {
middle: '',
end: ''
}
}
},
data: {
allData: 'The data is as follows: ',
partialData: 'The first {displayCnt} items are: ',
withName: 'the data for {name} is {value}',
withoutName: '{value}',
separator: {
middle: ', ',
end: '. '
}
}
}
};
echarts.registerLocale('EN', localeObj);
});
@@ -1,173 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports);
} else {
// Browser globals
factory({});
}
})(this, function(exports) {
/**
* Language: Korean.
*/
var localeObj = {
time: {
month: [
'1월', '2월', '3월', '4월', '5월', '6월',
'7월', '8월', '9월', '10월', '11월', '12월'
],
monthAbbr: [
'1월', '2월', '3월', '4월', '5월', '6월',
'7월', '8월', '9월', '10월', '11월', '12월'
],
dayOfWeek: [
'일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'
],
dayOfWeekAbbr: [
'일', '월', '화', '수', '목', '금', '토'
]
},
legend: {
selector: {
all: '모두 선택',
inverse: '선택 범위 반전'
}
},
toolbox: {
brush: {
title: {
rect: '사각형 선택',
polygon: '올가미 선택',
lineX: '수평 선택',
lineY: '수직 선택',
keep: '선택 유지',
clear: '선택 지우기'
}
},
dataView: {
title: '날짜 보기',
lang: ['날짜 보기', '닫기', '새로 고침']
},
dataZoom: {
title: {
zoom: '확대/축소',
back: '확대/축소 초기화'
}
},
magicType: {
title: {
line: '꺽은선 그래프로 변경',
bar: '막대 그래프로 변경',
stack: '스택',
tiled: '타일'
}
},
restore: {
title: '복구'
},
saveAsImage: {
title: '이미지로 저장',
lang: ['이미지를 저장하려면 마우스 오른쪽 버튼을 클릭하세요.']
}
},
series: {
typeNames: {
pie: '원 그래프',
bar: '막대 그래프',
line: '꺽은선 그래프',
scatter: '산점도',
effectScatter: '물결 효과 산점도',
radar: '방사형 그래프',
tree: '트리',
treemap: '트리맵',
boxplot: '상자 수염 그래프',
candlestick: '캔들스틱 차트',
k: 'K 라인 차트',
heatmap: '히트 맵',
map: '지도',
parallel: '평행 좌표 맵',
lines: '선',
graph: '관계 그래프',
sankey: '산키 다이어그램',
funnel: '깔때기형 그래프',
gauge: '계기',
pictorialBar: '픽토그램 차트',
themeRiver: '스트림 그래프',
sunburst: '선버스트 차트'
}
},
aria: {
general: {
withTitle: '"{title}"에 대한 차트입니다.',
withoutTitle: '차트입니다.'
},
series: {
single: {
prefix: '',
withName: ' 차트 유형은 {seriesType}이며 {seriesName}을 표시합니다.',
withoutName: ' 차트 유형은 {seriesType}입니다.'
},
multiple: {
prefix: '. {seriesCount} 하나의 차트 시리즈로 구성됩니다.',
withName: ' {seriesId}번째 시리즈는 {seriesName}을 나타내는 {seriesType} representing.',
withoutName: ' {seriesId}번째 시리즈는 {seriesType}입니다.',
separator: {
middle: '',
end: ''
}
}
},
data: {
allData: '데이터: ',
partialData: '첫번째 {displayCnt} 아이템: ',
withName: '{name}의 데이터는 {value}',
withoutName: '{value}',
separator: {
middle: ', ',
end: '. '
}
}
}
};
for (var key in localeObj) {
if (localeObj.hasOwnProperty(key)) {
exports[key] = localeObj[key];
}
}
});
@@ -1,169 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
/**
* Language: Korean.
*/
var localeObj = {
time: {
month: [
'1월', '2월', '3월', '4월', '5월', '6월',
'7월', '8월', '9월', '10월', '11월', '12월'
],
monthAbbr: [
'1월', '2월', '3월', '4월', '5월', '6월',
'7월', '8월', '9월', '10월', '11월', '12월'
],
dayOfWeek: [
'일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'
],
dayOfWeekAbbr: [
'일', '월', '화', '수', '목', '금', '토'
]
},
legend: {
selector: {
all: '모두 선택',
inverse: '선택 범위 반전'
}
},
toolbox: {
brush: {
title: {
rect: '사각형 선택',
polygon: '올가미 선택',
lineX: '수평 선택',
lineY: '수직 선택',
keep: '선택 유지',
clear: '선택 지우기'
}
},
dataView: {
title: '날짜 보기',
lang: ['날짜 보기', '닫기', '새로 고침']
},
dataZoom: {
title: {
zoom: '확대/축소',
back: '확대/축소 초기화'
}
},
magicType: {
title: {
line: '꺽은선 그래프로 변경',
bar: '막대 그래프로 변경',
stack: '스택',
tiled: '타일'
}
},
restore: {
title: '복구'
},
saveAsImage: {
title: '이미지로 저장',
lang: ['이미지를 저장하려면 마우스 오른쪽 버튼을 클릭하세요.']
}
},
series: {
typeNames: {
pie: '원 그래프',
bar: '막대 그래프',
line: '꺽은선 그래프',
scatter: '산점도',
effectScatter: '물결 효과 산점도',
radar: '방사형 그래프',
tree: '트리',
treemap: '트리맵',
boxplot: '상자 수염 그래프',
candlestick: '캔들스틱 차트',
k: 'K 라인 차트',
heatmap: '히트 맵',
map: '지도',
parallel: '평행 좌표 맵',
lines: '선',
graph: '관계 그래프',
sankey: '산키 다이어그램',
funnel: '깔때기형 그래프',
gauge: '계기',
pictorialBar: '픽토그램 차트',
themeRiver: '스트림 그래프',
sunburst: '선버스트 차트'
}
},
aria: {
general: {
withTitle: '"{title}"에 대한 차트입니다.',
withoutTitle: '차트입니다.'
},
series: {
single: {
prefix: '',
withName: ' 차트 유형은 {seriesType}이며 {seriesName}을 표시합니다.',
withoutName: ' 차트 유형은 {seriesType}입니다.'
},
multiple: {
prefix: '. {seriesCount} 하나의 차트 시리즈로 구성됩니다.',
withName: ' {seriesId}번째 시리즈는 {seriesName}을 나타내는 {seriesType} representing.',
withoutName: ' {seriesId}번째 시리즈는 {seriesType}입니다.',
separator: {
middle: '',
end: ''
}
}
},
data: {
allData: '데이터: ',
partialData: '첫번째 {displayCnt} 아이템: ',
withName: '{name}의 데이터는 {value}',
withoutName: '{value}',
separator: {
middle: ', ',
end: '. '
}
}
}
};
echarts.registerLocale('KO', localeObj);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#f2385a',
'#f5a503',
'#4ad9d9',
'#f7879c',
'#c1d7a8',
'#4dffd2',
'#fccfd7',
'#d5f6f6'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#f2385a'
}
},
visualMap: {
color: ['#f2385a', '#f5a503']
},
toolbox: {
color: ['#f2385a', '#f2385a', '#f2385a', '#f2385a']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#f2385a',
type: 'dashed'
},
crossStyle: {
color: '#f2385a'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#f2385a' // Handle color
},
timeline: {
lineStyle: {
color: '#f2385a'
},
controlStyle: {
color: '#f2385a',
borderColor: '#f2385a'
}
},
candlestick: {
itemStyle: {
color: '#f2385a',
color0: '#f5a503'
},
lineStyle: {
width: 1,
color: '#f2385a',
color0: '#f5a503'
},
areaStyle: {
color: '#c1d7a8',
color0: '#4ad9d9'
}
},
map: {
itemStyle: {
color: '#f2385a'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#f2385a'
},
linkStyle: {
color: '#f2385a'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#f5a503'],
[0.8, '#f2385a'],
[1, '#c1d7a8']
],
width: 8
}
}
}
};
echarts.registerTheme('azul', theme);
});
@@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#001727',
'#805500',
'#ffff00',
'#ffd11a',
'#f2d71f',
'#f2be19',
'#f3a81a',
'#fff5cc'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#001727'
}
},
visualMap: {
color: ['#001727', '#805500']
},
toolbox: {
color: ['#001727', '#001727', '#001727', '#001727']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#001727',
type: 'dashed'
},
crossStyle: {
color: '#001727'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#001727' // Handle color
},
timeline: {
lineStyle: {
color: '#001727'
},
controlStyle: {
color: '#001727',
borderColor: '#001727'
}
},
candlestick: {
itemStyle: {
color: '#f3a81a',
color0: '#ffff00'
},
lineStyle: {
width: 1,
color: '#ffff00',
color0: '#f3a81a'
},
areaStyle: {
color: '#805500',
color0: '#ffff00'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#f3a81a',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#805500'
}
},
map: {
itemStyle: {
color: '#ffd11a'
},
areaStyle: {
color: '#f2be19'
},
label: {
color: '#ffd11a'
}
},
graph: {
itemStyle: {
color: '#001727'
},
linkStyle: {
color: '#001727'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#f2d71f'],
[0.8, '#001727'],
[1, '#ffff00']
],
width: 8
}
}
}
};
echarts.registerTheme('bee-inspired', theme);
});
@@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#1790cf',
'#1bb2d8',
'#99d2dd',
'#88b0bb',
'#1c7099',
'#038cc4',
'#75abd0',
'#afd6dd'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#1790cf'
}
},
visualMap: {
color: ['#1790cf', '#a2d4e6']
},
toolbox: {
color: ['#1790cf', '#1790cf', '#1790cf', '#1790cf']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#1790cf',
type: 'dashed'
},
crossStyle: {
color: '#1790cf'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
handleColor: '#1790cf' // Handle color
},
timeline: {
lineStyle: {
color: '#1790cfa'
},
controlStyle: {
color: '#1790cf',
borderColor: '#1790cf'
}
},
candlestick: {
itemStyle: {
color: '#1bb2d8',
color0: '#99d2dd'
},
lineStyle: {
width: 1,
color: '#1c7099',
color0: '#88b0bb'
},
areaStyle: {
color: '#1790cf',
color0: '#1bb2d8'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#1bb2d8',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#1790cf'
}
},
graph: {
itemStyle: {
color: '#1bb2d8'
},
linkStyle: {
color: '#88b0bb'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '99d2dd'
},
label: {
color: '#c12e34'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#1bb2d8'],
[0.8, '#1790cf'],
[1, '#1c7099']
],
width: 8
}
}
}
};
echarts.registerTheme('blue', theme);
});
@@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#fad089',
'#ff9c5b',
'#f5634a',
'#ed303c',
'#3b8183',
'#f7826e',
'#faac9e',
'#fcd5cf'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#fad089'
}
},
visualMap: {
color: ['#fad089', '#a2d4e6']
},
toolbox: {
color: ['#fad089', '#fad089', '#fad089', '#fad089']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#fad089',
type: 'dashed'
},
crossStyle: {
color: '#fad089'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
handleColor: '#fad089' // Handle color
},
timeline: {
lineStyle: {
color: '#fad089'
},
controlStyle: {
color: '#fad089',
borderColor: '#fad089'
}
},
candlestick: {
itemStyle: {
color: '#ff9c5b',
color0: '#f5634a'
},
lineStyle: {
width: 1,
color: '#3b8183',
color0: '#ed303c'
},
areaStyle: {
color: '#fad089',
color0: '#ed303c'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#fad089',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#ed303c'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#f5634a'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#f5634a'
},
linkStyle: {
color: '#fad089'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#ff9c5b'],
[0.8, '#fad089'],
[1, '#3b8183']
],
width: 8
}
}
}
};
echarts.registerTheme('caravan', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#f0d8A8',
'#3d1c00',
'#86b8b1',
'#f2d694',
'#fa2a00',
'#ff8066',
'#ffd5cc',
'#f9edd2'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#f0d8A8'
}
},
visualMap: {
color: ['#f0d8A8', '#3d1c00']
},
toolbox: {
color: ['#f0d8A8', '#f0d8A8', '#f0d8A8', '#f0d8A8']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#f0d8A8',
type: 'dashed'
},
crossStyle: {
color: '#f0d8A8'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#f0d8A8' // Handle color
},
timeline: {
lineStyle: {
color: '#f0dba8'
},
controlStyle: {
color: '#f0dba8',
borderColor: '#f0dba8'
}
},
candlestick: {
itemStyle: {
color: '#3d1c00',
color0: '#86b8b1'
},
lineStyle: {
width: 1,
color: '#fa2a00',
color0: '#f2d694'
},
areaStyle: {
color: '#f0d8A8',
color0: '#86b8b1'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#86b8b1'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#3d1c00'
},
linkStyle: {
color: '#f0d8A8'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#3d1c00'],
[0.8, '#f0d8A8'],
[1, '#fa2a00']
],
width: 8
}
}
}
};
echarts.registerTheme('carp', theme);
});
@@ -1,180 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#b21ab4',
'#6f0099',
'#2a2073',
'#0b5ea8',
'#17aecc',
'#b3b3ff',
'#eb99ff',
'#fae6ff',
'#e6f2ff',
'#eeeeee'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#00aecd'
}
},
visualMap: {
color: ['#00aecd', '#a2d4e6']
},
toolbox: {
color: ['#00aecd', '#00aecd', '#00aecd', '#00aecd']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#00aecd',
type: 'dashed'
},
crossStyle: {
color: '#00aecd'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
handleColor: '#00aecd' // Handle color
},
timeline: {
lineStyle: {
color: '#00aecd'
},
controlStyle: {
color: '#00aecd',
borderColor: '00aecd'
}
},
candlestick: {
itemStyle: {
color: '#00aecd',
color0: '#a2d4e6'
},
lineStyle: {
width: 1,
color: '#00aecd',
color0: '#a2d4e6'
},
areaStyle: {
color: '#b21ab4',
color0: '#0b5ea8'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#b21ab4',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#0b5ea8'
}
},
graph: {
itemStyle: {
color: '#b21ab4'
},
linkStyle: {
color: '#2a2073'
}
},
map: {
itemStyle: {
color: '#c12e34'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#dddddd'],
[0.8, '#00aecd'],
[1, '#f5ccff']
],
width: 8
}
}
}
};
echarts.registerTheme('cool', theme);
});
@@ -1,164 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var contrastColor = '#eee';
var axisCommon = function() {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
axisTick: {
lineStyle: {
color: contrastColor
}
},
axisLabel: {
color: contrastColor
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#aaa'
}
},
splitArea: {
areaStyle: {
color: contrastColor
}
}
};
};
var colorPalette = [
'#00305a',
'#004b8d',
'#0074d9',
'#4192d9',
'#7abaf2',
'#99cce6',
'#d6ebf5',
'#eeeeee'
];
var theme = {
color: colorPalette,
backgroundColor: '#333',
tooltip: {
axisPointer: {
lineStyle: {
color: contrastColor
},
crossStyle: {
color: contrastColor
}
}
},
legend: {
textStyle: {
color: contrastColor
}
},
title: {
textStyle: {
color: contrastColor
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#00305a' // Handle color
},
timeline: {
itemStyle: {
color: colorPalette[1]
},
lineStyle: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
},
label: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#004b8d'],
[0.8, '#00305a'],
[1, '#7abaf2']
],
width: 8
}
}
}
};
theme.categoryAxis.splitLine.show = false;
echarts.registerTheme('dark-blue', theme);
});
@@ -1,164 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var contrastColor = '#eee';
var axisCommon = function() {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
axisTick: {
lineStyle: {
color: contrastColor
}
},
axisLabel: {
color: contrastColor
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#aaa'
}
},
splitArea: {
areaStyle: {
color: contrastColor
}
}
};
};
var colorPalette = [
'#458c6b',
'#f2da87',
'#d9a86c',
'#d94436',
'#a62424',
'#76bc9b',
'#cce6da',
'#eeeeee'
];
var theme = {
color: colorPalette,
backgroundColor: '#333',
tooltip: {
axisPointer: {
lineStyle: {
color: contrastColor
},
crossStyle: {
color: contrastColor
}
}
},
legend: {
textStyle: {
color: contrastColor
}
},
title: {
textStyle: {
color: contrastColor
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#458c6b' // Handle color
},
timeline: {
itemStyle: {
color: colorPalette[1]
},
lineStyle: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
},
label: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#f2da87'],
[0.8, '#458c6b'],
[1, '#a62424']
],
width: 8
}
}
}
};
theme.categoryAxis.splitLine.show = false;
echarts.registerTheme('dark-bold', theme);
});
@@ -1,164 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var contrastColor = '#eee';
var axisCommon = function() {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
axisTick: {
lineStyle: {
color: contrastColor
}
},
axisLabel: {
color: contrastColor
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#aaa'
}
},
splitArea: {
areaStyle: {
color: contrastColor
}
}
};
};
var colorPalette = [
'#52656b',
'#ff3b77',
'#a3cc00',
'#ffffff',
'#b8b89f',
'#ffccdb',
'#e5ff80',
'#f4f4f0'
];
var theme = {
color: colorPalette,
backgroundColor: '#333',
tooltip: {
axisPointer: {
lineStyle: {
color: contrastColor
},
crossStyle: {
color: contrastColor
}
}
},
legend: {
textStyle: {
color: contrastColor
}
},
title: {
textStyle: {
color: contrastColor
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#52656b' // Handle color
},
timeline: {
itemStyle: {
color: colorPalette[1]
},
lineStyle: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
},
label: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#ff3b77'],
[0.8, '#52656b'],
[1, '#b8b89f']
],
width: 8
}
}
}
};
theme.categoryAxis.splitLine.show = false;
echarts.registerTheme('dark-digerati', theme);
});
@@ -1,164 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var contrastColor = '#eee';
var axisCommon = function() {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
axisTick: {
lineStyle: {
color: contrastColor
}
},
axisLabel: {
color: contrastColor
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#aaa'
}
},
splitArea: {
areaStyle: {
color: contrastColor
}
}
};
};
var colorPalette = [
'#00a8c6',
'#40c0cb',
'#ebd3ad',
'#aee239',
'#8fbe00',
'#33e0ff',
'#b3f4ff',
'#e6ff99'
];
var theme = {
color: colorPalette,
backgroundColor: '#333',
tooltip: {
axisPointer: {
lineStyle: {
color: contrastColor
},
crossStyle: {
color: contrastColor
}
}
},
legend: {
textStyle: {
color: contrastColor
}
},
title: {
textStyle: {
color: contrastColor
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#00a8c6' // Handle color
},
timeline: {
itemStyle: {
color: colorPalette[1]
},
lineStyle: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
},
label: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#40c0cb'],
[0.8, '#00a8c6'],
[1, '#8fbe00']
],
width: 8
}
}
}
};
theme.categoryAxis.splitLine.show = false;
echarts.registerTheme('dark-fresh-cut', theme);
});
@@ -1,164 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var contrastColor = '#eee';
var axisCommon = function() {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
axisTick: {
lineStyle: {
color: contrastColor
}
},
axisLabel: {
color: contrastColor
},
splitLine: {
lineStyle: {
type: 'dashed',
color: '#aaa'
}
},
splitArea: {
areaStyle: {
color: contrastColor
}
}
};
};
var colorPalette = [
'#cc0e00',
'#ff1a0a',
'#ff8880',
'#ffc180',
'#ffc2b0',
'#ffffff',
'#ff8880',
'#ffe6e6'
];
var theme = {
color: colorPalette,
backgroundColor: '#333',
tooltip: {
axisPointer: {
lineStyle: {
color: contrastColor
},
crossStyle: {
color: contrastColor
}
}
},
legend: {
textStyle: {
color: contrastColor
}
},
title: {
textStyle: {
color: contrastColor
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#cc0e00' // Handle color
},
timeline: {
itemStyle: {
color: colorPalette[1]
},
lineStyle: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
},
label: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#ff1a0a'],
[0.8, '#cc0e00'],
[1, '#ffc2b0']
],
width: 8
}
}
}
};
theme.categoryAxis.splitLine.show = false;
echarts.registerTheme('dark-mushroom', theme);
});
@@ -1,224 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var contrastColor = '#B9B8CE';
var backgroundColor = '#100C2A';
var axisCommon = function () {
return {
axisLine: {
lineStyle: {
color: contrastColor
}
},
splitLine: {
lineStyle: {
color: '#484753'
}
},
splitArea: {
areaStyle: {
color: ['rgba(255,255,255,0.02)', 'rgba(255,255,255,0.05)']
}
},
minorSplitLine: {
lineStyle: {
color: '#20203B'
}
}
};
};
var colorPalette = [
'#4992ff',
'#7cffb2',
'#fddd60',
'#ff6e76',
'#58d9f9',
'#05c091',
'#ff8a45',
'#8d48e3',
'#dd79ff'
];
var theme = {
darkMode: true,
color: colorPalette,
backgroundColor: backgroundColor,
axisPointer: {
lineStyle: {
color: '#817f91'
},
crossStyle: {
color: '#817f91'
},
label: {
// TODO Contrast of label backgorundColor
color: '#fff'
}
},
legend: {
textStyle: {
color: contrastColor
}
},
textStyle: {
color: contrastColor
},
title: {
textStyle: {
color: '#EEF1FA'
},
subtextStyle: {
color: '#B9B8CE'
}
},
toolbox: {
iconStyle: {
borderColor: contrastColor
}
},
dataZoom: {
borderColor: '#71708A',
textStyle: {
color: contrastColor
},
brushStyle: {
color: 'rgba(135,163,206,0.3)'
},
handleStyle: {
color: '#353450',
borderColor: '#C5CBE3'
},
moveHandleStyle: {
color: '#B0B6C3',
opacity: 0.3
},
fillerColor: 'rgba(135,163,206,0.2)',
emphasis: {
handleStyle: {
borderColor: '#91B7F2',
color: '#4D587D'
},
moveHandleStyle: {
color: '#636D9A',
opacity: 0.7
}
},
dataBackground: {
lineStyle: {
color: '#71708A',
width: 1
},
areaStyle: {
color: '#71708A'
}
},
selectedDataBackground: {
lineStyle: {
color: '#87A3CE'
},
areaStyle: {
color: '#87A3CE'
}
}
},
visualMap: {
textStyle: {
color: contrastColor
}
},
timeline: {
lineStyle: {
color: contrastColor
},
label: {
color: contrastColor
},
controlStyle: {
color: contrastColor,
borderColor: contrastColor
}
},
calendar: {
itemStyle: {
color: backgroundColor
},
dayLabel: {
color: contrastColor
},
monthLabel: {
color: contrastColor
},
yearLabel: {
color: contrastColor
}
},
timeAxis: axisCommon(),
logAxis: axisCommon(),
valueAxis: axisCommon(),
categoryAxis: axisCommon(),
line: {
symbol: 'circle'
},
graph: {
color: colorPalette
},
gauge: {
title: {
color: contrastColor
}
},
candlestick: {
itemStyle: {
color: '#FD1050',
color0: '#0CF49B',
borderColor: '#FD1050',
borderColor0: '#0CF49B'
}
}
};
theme.categoryAxis.splitLine.show = false;
echarts.registerTheme('dark', theme);
});
@@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#59535e',
'#e7dcef',
'#f1baf3',
'#5d4970',
'#372049',
'#c0b2cd',
'#ffccff',
'#f2f0f5'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#59535e'
}
},
visualMap: {
color: ['#59535e', '#e7dcef']
},
toolbox: {
color: ['#59535e', '#59535e', '#59535e', '#59535e']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#59535e',
type: 'dashed'
},
crossStyle: {
color: '#59535e'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#59535e' // Handle color
},
timeline: {
lineStyle: {
color: '#59535e'
},
controlStyle: {
color: '#59535e',
borderColor: '#59535e'
}
},
candlestick: {
itemStyle: {
color: '#e7dcef',
color0: '#f1baf3'
},
lineStyle: {
width: 1,
color: '#372049',
color0: '#5d4970'
},
areaStyle: {
color: '#59535e',
color0: '#e7dcef'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#59535e',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#e7dcef'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#f1baf3'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#59535e'
},
linkStyle: {
color: '#59535e'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#e7dcef'],
[0.8, '#59535e'],
[1, '#372049']
],
width: 8
}
}
}
};
echarts.registerTheme('eduardo', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#313b23',
'#494f2b',
'#606233',
'#d6b77b',
'#0e0e0e',
'#076278',
'#808080',
'#e7d5b1'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#313b23'
}
},
visualMap: {
color: ['#313b23', '#494f2b']
},
toolbox: {
color: ['#313b23', '#313b23', '#313b23', '#313b23']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#313b23',
type: 'dashed'
},
crossStyle: {
color: '#313b23'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#313b23' // Handle color
},
timeline: {
lineStyle: {
color: '#313b23'
},
controlStyle: {
color: '#313b23',
borderColor: '#313b23'
}
},
candlestick: {
itemStyle: {
color: '#494f2b',
color0: '#606233'
},
lineStyle: {
width: 1,
color: '#0e0e0e',
color0: '#d6b77b'
},
areaStyle: {
color: '#494f2b',
color0: '#d6b77b'
}
},
map: {
itemStyle: {
color: '#606233'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#494f2b'
},
linkStyle: {
color: '#313b23'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#494f2b'],
[0.8, '#313b23'],
[1, '0e0e0e']
],
width: 8
}
}
}
};
echarts.registerTheme('forest', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#00a8c6',
'#40c0cb',
'#f0dec2',
'#aee239',
'#8fbe00',
'#33e0ff',
'#b3f4ff',
'#e6ff99'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#00a8c6'
}
},
visualMap: {
color: ['#00a8c6', '#a2d4e6']
},
toolbox: {
color: ['#00a8c6', '#00a8c6', '#00a8c6', '#00a8c6']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#00a8c6',
type: 'dashed'
},
crossStyle: {
color: '#00a8c6'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
handleColor: '#00a8c6' // Handle color
},
timeline: {
lineStyle: {
color: '#00a8c6'
},
controlStyle: {
color: '#00a8c6',
borderColor: '#00a8c6'
}
},
candlestick: {
itemStyle: {
color: '#40c0cb',
color0: '#f0dec2'
},
lineStyle: {
width: 1,
color: '#8fbe00',
color0: '#aee239'
},
areaStyle: {
color: '#00a8c6',
color0: '#aee239'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#f0dec2'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#f0dec2'
},
linkStyle: {
color: '#00a8c6'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#40c0cb'],
[0.8, '#00a8c6'],
[1, '#8fbe00']
],
width: 8
}
}
}
};
echarts.registerTheme('fresh-cut', theme);
});
@@ -1,178 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#ffcb6a',
'#ffa850',
'#ffe2c4',
'#e5834e',
'#ffb081',
'#f7826e',
'#faac9e',
'#fcd5cf'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#ffcb6a'
}
},
visualMap: {
color: ['#ffcb6a', '#ffa850']
},
toolbox: {
color: ['#ffcb6a', '#ffcb6a', '#ffcb6a', '#ffcb6a']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#ffcb6a',
type: 'dashed'
},
crossStyle: {
color: '#ffcb6a'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#ffcb6a' // Handle color
},
timeline: {
lineStyle: {
color: '#ffcb6a'
},
controlStyle: {
color: '#ffcb6a',
borderColor: '#ffcb6a'
}
},
candlestick: {
itemStyle: {
color: '#ffa850',
color0: '#ffe2c4'
},
lineStyle: {
width: 1,
color: '#ffb081',
color0: '#e5834e'
},
areaStyle: {
color: '#e5834e',
color0: '#fcd5cf'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#fcd5cf',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#e5834e'
}
},
map: {
itemStyle: {
color: '#ffe2c4'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#f2385a'
},
linkStyle: {
color: '#ffcb6a'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#ffa850'],
[0.8, '#ffcb6a'],
[1, '#ffb081']
],
width: 8
}
}
}
};
echarts.registerTheme('fruit', theme);
});
@@ -1,220 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#757575',
'#c7c7c7',
'#dadada',
'#8b8b8b',
'#b5b5b5',
'#e9e9e9'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#757575'
}
},
dataRange: {
color: ['#636363', '#dcdcdc']
},
toolbox: {
color: ['#757575', '#757575', '#757575', '#757575']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#757575',
type: 'dashed'
},
crossStyle: {
color: '#757575'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(117,117,117,0.2)', // Fill the color
handleColor: '#757575' // Handle color
},
grid: {
borderWidth: 0
},
categoryAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#757575'
}
},
splitLine: {
// Separation line
lineStyle: {
// Property 'lineStyle' (see lineStyle) controls line styles
color: ['#eee']
}
}
},
valueAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#757575'
}
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
}
},
splitLine: {
// Separation line
lineStyle: {
// Property 'lineStyle' (see lineStyle) controls line styles
color: ['#eee']
}
}
},
timeline: {
lineStyle: {
color: '#757575'
},
controlStyle: {
color: '#757575',
borderColor: '#757575'
}
},
candlestick: {
itemStyle: {
color: '#8b8b8b',
color0: '#dadada'
},
lineStyle: {
width: 1,
color: '#757575',
color0: '#c7c7c7'
},
areaStyle: {
color: '#757575',
color0: '#e9e9e9'
}
},
map: {
itemStyle: {
color: '#c7c7c7'
},
areaStyle: {
color: 'ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#e9e9e9'
},
linkStyle: {
color: '#757575'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#e9e9e9',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#757575'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#b5b5b5'],
[0.8, '#757575'],
[1, '#5c5c5c']
],
width: 8
}
}
}
};
echarts.registerTheme('gray', theme);
});
@@ -1,222 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#408829',
'#68a54a',
'#a9cba2',
'#86b379',
'#397b29',
'#8abb6f',
'#759c6a',
'#bfd3b7'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#408829'
}
},
visualMap: {
color: ['408829', '#a9cba2']
},
toolbox: {
color: ['#408829', '#408829', '#408829', '#408829']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#408829',
type: 'dashed'
},
crossStyle: {
color: '#408829'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(64,136,41,0.2)', // Fill the color
handleColor: '#408829' // Handle color
},
grid: {
borderWidth: 0
},
categoryAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#408829'
}
},
splitLine: {
// Separation line
lineStyle: {
// Property 'lineStyle' (see lineStyle) controls line styles
color: ['#eee']
}
}
},
valueAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#408829'
}
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
}
},
splitLine: {
// Separation line
lineStyle: {
// Property 'lineStyle' (see lineStyle) controls line styles
color: ['#eee']
}
}
},
timeline: {
lineStyle: {
color: '#408829'
},
controlStyle: {
color: '#408829',
borderColor: '#408829'
}
},
candlestick: {
itemStyle: {
color: '#68a54a',
color0: '#a9cba2'
},
lineStyle: {
width: 1,
color: '#408829',
color0: '#86b379'
},
areaStyle: {
color: '#408829',
color0: '#bfd3b7'
}
},
graph: {
itemStyle: {
color: '#bfd3b7'
},
linkStyle: {
color: '#408829'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#bfd3b7',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#408829'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#408829'
},
label: {
color: '#000'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#86b379'],
[0.8, '#68a54a'],
[1, '#408829']
],
width: 8
}
}
}
};
echarts.registerTheme('green', theme);
});
@@ -1,263 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#44B7D3',
'#E42B6D',
'#F4E24E',
'#FE9616',
'#8AED35',
'#ff69b4',
'#ba55d3',
'#cd5c5c',
'#ffa500',
'#40e0d0',
'#E95569',
'#ff6347',
'#7b68ee',
'#00fa9a',
'#ffd700',
'#6699FF',
'#ff6666',
'#3cb371',
'#b8860b',
'#30e0e0'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#8A826D'
}
},
dataRange: {
x: 'right',
y: 'center',
itemWidth: 5,
itemHeight: 25,
color: ['#E42B6D', '#F9AD96'],
text: ['High', 'Low'], // Text, default is numeric text
textStyle: {
color: '#8A826D' // Range text color
}
},
toolbox: {
color: ['#E95569', '#E95569', '#E95569', '#E95569'],
effectiveColor: '#ff4500',
itemGap: 8
},
tooltip: {
backgroundColor: 'rgba(138,130,109,0.7)', // Prompt background color, default is black with a transparency of 0.7
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#6B6455',
type: 'dashed'
},
crossStyle: {
color: '#A6A299'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: 'rgba(130,197,209,0.6)', // Data background color
fillerColor: 'rgba(233,84,105,0.1)', // Fill the color
handleColor: 'rgba(107,99,84,0.8)' // Handle color
},
grid: {
borderWidth: 0
},
categoryAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#6B6455'
}
},
splitLine: {
// separate line
show: false
}
},
valueAxis: {
axisLine: {
// Coordinate axis
show: true
},
splitArea: {
show: false
},
splitLine: {
// separate line
lineStyle: {
// Property 'lineStyle' controls line styles
color: ['#FFF'],
type: 'dashed'
}
}
},
polar: {
axisLine: {
// Coordinate axis
lineStyle: {
// // Property 'lineStyle' controls line styles
color: '#ddd'
}
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(250,250,250,0.2)', 'rgba(200,200,200,0.2)']
}
},
splitLine: {
lineStyle: {
color: '#ddd'
}
}
},
timeline: {
lineStyle: {
color: '#6B6455'
},
controlStyle: {
color: '#6B6455',
borderColor: '#6B6455'
}
},
line: {
smooth: true,
symbol: 'emptyCircle', // Inflection point graphic type
symbolSize: 3 // Inflection point graphic size
},
candlestick: {
itemStyle: {
color: '#e42B6d',
color0: '#44B7d3'
},
lineStyle: {
width: 1,
color: '#e42B6d',
color0: '#44B7d3'
},
areaStyle: {
color: '#fe994e',
color0: '#e42B6d'
}
},
map: {
itemStyle: {
color: '#6b6455'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#e42B6d'
}
},
graph: {
itemStyle: {
color: '#e42B6d'
},
linkStyle: {
color: '#6b6455'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#e42B6d',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#6b6455'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#44B7D3'],
[0.8, '#6B6455'],
[1, '#E42B6D']
],
width: 8
}
}
}
};
echarts.registerTheme('helianthus', theme);
});
@@ -1,236 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#C1232B',
'#27727B',
'#FCCE10',
'#E87C25',
'#B5C334',
'#FE8463',
'#9BCA63',
'#FAD860',
'#F3A43B',
'#60C0DD',
'#D7504B',
'#C6E579',
'#F4E001',
'#F0805A',
'#26C0C0'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#27727B'
}
},
visualMap: {
color: ['#C1232B', '#FCCE10']
},
toolbox: {
iconStyle: {
borderColor: colorPalette[0]
}
},
tooltip: {
backgroundColor: 'rgba(50,50,50,0.5)',
axisPointer: {
type: 'line',
lineStyle: {
color: '#27727B',
type: 'dashed'
},
crossStyle: {
color: '#27727B'
},
shadowStyle: {
color: 'rgba(200,200,200,0.3)'
}
}
},
dataZoom: {
dataBackgroundColor: 'rgba(181,195,52,0.3)',
fillerColor: 'rgba(181,195,52,0.2)',
handleColor: '#27727B'
},
categoryAxis: {
axisLine: {
lineStyle: {
color: '#27727B'
}
},
splitLine: {
show: false
}
},
valueAxis: {
axisLine: {
show: false
},
splitArea: {
show: false
},
splitLine: {
lineStyle: {
color: ['#ccc'],
type: 'dashed'
}
}
},
timeline: {
itemStyle: {
color: '#27727B'
},
lineStyle: {
color: '#27727B'
},
controlStyle: {
color: '#27727B',
borderColor: '#27727B'
},
symbol: 'emptyCircle',
symbolSize: 3
},
line: {
itemStyle: {
borderWidth: 2,
borderColor: '#fff',
lineStyle: {
width: 3
}
},
emphasis: {
itemStyle: {
borderWidth: 0
}
},
symbol: 'circle',
symbolSize: 3.5
},
candlestick: {
itemStyle: {
color: '#c1232b',
color0: '#b5c334'
},
lineStyle: {
width: 1,
color: '#c1232b',
color0: '#b5c334'
},
areaStyle: {
color: '#c1232b',
color0: '#27727b'
}
},
graph: {
itemStyle: {
color: '#c1232b'
},
linkStyle: {
color: '#b5c334'
}
},
map: {
itemStyle: {
color: '#f2385a',
areaColor: '#ddd',
borderColor: '#eee'
},
areaStyle: {
color: '#fe994e'
},
label: {
color: '#c1232b'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#B5C334'],
[0.8, '#27727B'],
[1, '#C1232B']
]
}
},
axisTick: {
splitNumber: 2,
length: 5,
lineStyle: {
color: '#fff'
}
},
axisLabel: {
color: '#fff'
},
splitLine: {
length: '5%',
lineStyle: {
color: '#fff'
}
},
title: {
offsetCenter: [0, -20]
}
}
};
echarts.registerTheme('infographic', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#cc0000',
'#002266',
'#ff9900',
'#006600',
'#8a150f',
'#076278',
'#808080',
'#f07b75'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#cc0000'
}
},
visualMap: {
color: ['#cc0000', '#002266']
},
toolbox: {
color: ['#cc0000', '#cc0000', '#cc0000', '#cc0000']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#cc0000',
type: 'dashed'
},
crossStyle: {
color: '#cc0000'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#cc0000' // Handle color
},
timeline: {
lineStyle: {
color: '#cc0000'
},
controlStyle: {
color: '#cc0000',
borderColor: '#cc0000'
}
},
candlestick: {
itemStyle: {
color: '#002266',
color0: '#ff9900'
},
lineStyle: {
width: 1,
color: '#8a150f',
color0: '#006600'
},
areaStyle: {
color: '#cc0000',
color0: '#ff9900'
}
},
map: {
itemStyle: {
color: '#ff9900'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#ff9900'
},
linkStyle: {
color: '#cc0000'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#002266'],
[0.8, '#cc0000'],
[1, '8a150f']
],
width: 8
}
}
}
};
echarts.registerTheme('inspired', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#e9e0d1',
'#91a398',
'#33605a',
'#070001',
'#68462b',
'#58a79c',
'#abd3ce',
'#eef6f5'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#e9e0d1'
}
},
visualMap: {
color: ['#e9e0d1', '#91a398']
},
toolbox: {
color: ['#e9e0d1', '#e9e0d1', '#e9e0d1', '#e9e0d1']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#e9e0d1',
type: 'dashed'
},
crossStyle: {
color: '#e9e0d1'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#e9e0d1' // Handle color
},
timeline: {
lineStyle: {
color: '#e9e0d1'
},
controlStyle: {
color: '#e9e0d1',
borderColor: '#e9e0d1'
}
},
candlestick: {
itemStyle: {
color: '#91a398',
color0: '#33605a'
},
lineStyle: {
width: 1,
color: '#68462b',
color0: '#070001'
},
areaStyle: {
color: '#91a398',
color0: '#abd3ce'
}
},
map: {
itemStyle: {
color: '#c12e34'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#33605a'
},
linkStyle: {
color: '#e9e0d1'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#91a398'],
[0.8, '#e9e0d1'],
[1, '#68462b']
],
width: 8
}
}
}
};
echarts.registerTheme('jazz', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#02151a',
'#043a47',
'#087891',
'#c8c8c8',
'#b31d14',
'#0b9cc1',
'#f2f2f2',
'#f07b75'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#02151a'
}
},
visualMap: {
color: ['#02151a', '#a2d4e6']
},
toolbox: {
color: ['#02151a', '#02151a', '#02151a', '#02151a']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#02151a',
type: 'dashed'
},
crossStyle: {
color: '#02151a'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
handleColor: '#02151a' // Handle color
},
timeline: {
lineStyle: {
color: '#02151a'
},
controlStyle: {
color: '#02151a',
borderColor: '#02151a'
}
},
candlestick: {
itemStyle: {
color: '#043a47',
color0: '#087891'
},
lineStyle: {
width: 1,
color: '#b31d14',
color0: '#c8c8c8'
},
areaStyle: {
color: '#087891',
color0: '#c8c8c8'
}
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#087891'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#c12e34'
},
linkStyle: {
color: '#02151a'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#043a47'],
[0.8, '#02151a'],
[1, '#b31d14']
],
width: 8
}
}
}
};
echarts.registerTheme('london', theme);
});
@@ -1,242 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#2ec7c9',
'#b6a2de',
'#5ab1ef',
'#ffb980',
'#d87a80',
'#8d98b3',
'#e5cf0d',
'#97b552',
'#95706d',
'#dc69aa',
'#07a2a4',
'#9a7fd1',
'#588dd5',
'#f5994e',
'#c05050',
'#59678c',
'#c9ab00',
'#7eb00a',
'#6f5553',
'#c14089'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#008acd'
}
},
visualMap: {
itemWidth: 15,
color: ['#5ab1ef', '#e0ffff']
},
toolbox: {
iconStyle: {
borderColor: colorPalette[0]
}
},
tooltip: {
borderWidth: 0,
backgroundColor: 'rgba(50,50,50,0.5)',
textStyle: {
color: '#FFF'
},
axisPointer: {
type: 'line',
lineStyle: {
color: '#008acd'
},
crossStyle: {
color: '#008acd'
},
shadowStyle: {
color: 'rgba(200,200,200,0.2)'
}
}
},
dataZoom: {
dataBackgroundColor: '#efefff',
fillerColor: 'rgba(182,162,222,0.2)',
handleColor: '#008acd'
},
grid: {
borderColor: '#eee'
},
categoryAxis: {
axisLine: {
lineStyle: {
color: '#008acd'
}
},
splitLine: {
lineStyle: {
color: ['#eee']
}
}
},
valueAxis: {
axisLine: {
lineStyle: {
color: '#008acd'
}
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
}
},
splitLine: {
lineStyle: {
color: ['#eee']
}
}
},
timeline: {
lineStyle: {
color: '#008acd'
},
controlStyle: {
color: '#008acd',
borderColor: '#008acd'
},
symbol: 'emptyCircle',
symbolSize: 3
},
line: {
smooth: true,
symbol: 'emptyCircle',
symbolSize: 3
},
candlestick: {
itemStyle: {
color: '#d87a80',
color0: '#2ec7c9'
},
lineStyle: {
width: 1,
color: '#d87a80',
color0: '#2ec7c9'
},
areaStyle: {
color: '#2ec7c9',
color0: '#b6a2de'
}
},
scatter: {
symbol: 'circle',
symbolSize: 4
},
map: {
itemStyle: {
color: '#ddd'
},
areaStyle: {
color: '#fe994e'
},
label: {
color: '#d87a80'
}
},
graph: {
itemStyle: {
color: '#d87a80'
},
linkStyle: {
color: '#2ec7c9'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#2ec7c9'],
[0.8, '#5ab1ef'],
[1, '#d87a80']
],
width: 10
}
},
axisTick: {
splitNumber: 10,
length: 15,
lineStyle: {
color: 'auto'
}
},
splitLine: {
length: 22,
lineStyle: {
color: 'auto'
}
},
pointer: {
width: 5
}
}
};
echarts.registerTheme('macarons', theme);
});
@@ -1,246 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#ed9678',
'#e7dac9',
'#cb8e85',
'#f3f39d',
'#c8e49c',
'#f16d7a',
'#f3d999',
'#d3758f',
'#dcc392',
'#2e4783',
'#82b6e9',
'#ff6347',
'#a092f1',
'#0a915d',
'#eaf889',
'#6699FF',
'#ff6666',
'#3cb371',
'#d5b158',
'#38b6b6'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#cb8e85'
}
},
dataRange: {
color: ['#cb8e85', '#e7dac9'], //颜色
//text:['高','低'], // 文本,默认为数值文本
textStyle: {
color: '#333' // 值域文字颜色
}
},
bar: {
barMinHeight: 0, // 最小高度改为0
// barWidth: null, // 默认自适应
barGap: '30%', // 柱间距离,默认为柱形宽度的30%,可设固定值
barCategoryGap: '20%', // 类目间柱形距离,默认为类目间距的20%,可设固定值
label: {
show: false
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
// 'inside'|'left'|'right'|'top'|'bottom'
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
},
itemStyle: {
// color: '各异',
barBorderColor: '#fff', // 柱条边线
barBorderRadius: 0, // 柱条边线圆角,单位px,默认为0
barBorderWidth: 1 // 柱条边线线宽,单位px,默认为1
},
emphasis: {
itemStyle: {
// color: '各异',
barBorderColor: 'rgba(0,0,0,0)', // 柱条边线
barBorderRadius: 0, // 柱条边线圆角,单位px,默认为0
barBorderWidth: 1, // 柱条边线线宽,单位px,默认为1
},
label: {
show: false
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
// 'inside'|'left'|'right'|'top'|'bottom'
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
}
}
},
line: {
label: {
show: false
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
// 'inside'|'left'|'right'|'top'|'bottom'
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
},
itemStyle: {
// color: 各异,
},
emphasis: {
// color: 各异,
label: {
show: false
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
// 'inside'|'left'|'right'|'top'|'bottom'
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
}
},
lineStyle: {
width: 2,
type: 'solid',
shadowColor: 'rgba(0,0,0,0)', //默认透明
shadowBlur: 5,
shadowOffsetX: 3,
shadowOffsetY: 3
},
//smooth : false,
//symbol: null, // 拐点图形类型
symbolSize: 2, // 拐点图形大小
//symbolRotate : null, // 拐点图形旋转控制
showAllSymbol: false // 标志图形默认只有主轴显示(随主轴标签间隔隐藏策略)
},
candlestick: {
itemStyle: {
color: '#fe9778',
color0: '#e7dac9'
},
lineStyle: {
width: 1,
color: '#f78766',
color0: '#f1ccb8'
},
areaStyle: {
color: '#e7dac9',
color0: '#c8e49c'
}
},
// 饼图默认参数
pie: {
center: ['50%', '50%'], // 默认全局居中
radius: [0, '75%'],
clockWise: false, // 默认逆时针
startAngle: 90,
minAngle: 0, // 最小角度改为0
selectedOffset: 10, // 选中是扇区偏移量
label: {
show: true,
position: 'outer',
color: '#1b1b1b',
lineStyle: { color: '#1b1b1b' }
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
},
itemStyle: {
// color: 各异,
borderColor: '#fff',
borderWidth: 1
},
labelLine: {
show: true,
length: 20,
lineStyle: {
// color: 各异,
width: 1,
type: 'solid'
}
}
},
map: {
itemStyle: {
color: '#ddd',
borderColor: '#fff',
borderWidth: 1
},
areaStyle: {
color: '#f3f39d'
},
label: {
show: false,
color: 'rgba(139,69,19,1)'
},
showLegendSymbol: true
},
graph: {
itemStyle: {
color: '#d87a80'
},
linkStyle: {
strokeColor: '#a17e6e'
},
nodeStyle: {
brushType: 'both',
strokeColor: '#a17e6e'
},
label: {
show: false
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#ed9678'],
[0.8, '#e7dac9'],
[1, '#cb8e85']
],
width: 8
}
}
}
};
echarts.registerTheme('macarons2', theme);
});
@@ -1,155 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#8aedd5',
'#93bc9e',
'#cef1db',
'#7fe579',
'#a6d7c2',
'#bef0bb',
'#99e2vb',
'#94f8a8',
'#7de5b8',
'#4dfb70'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#8aedd5'
}
},
toolbox: {
color: ['#8aedd5', '#8aedd5', '#8aedd5', '#8aedd5']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#8aedd5',
type: 'dashed'
},
crossStyle: {
color: '#8aedd5'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(64,136,41,0.2)', // Fill the color
handleColor: '#408829' // Handle color
},
dataRange: {
color: ['#93bc92', '#bef0bb']
},
candlestick: {
itemStyle: {
color: '#8aedd5',
color0: '#7fe579'
},
lineStyle: {
width: 1,
color: '#8aedd5',
color0: '#7fe579'
},
areaStyle: {
color: '#8aedd5',
color0: '#93bc9e'
}
},
graph: {
itemStyle: {
color: '#8aedd5'
},
linkStyle: {
color: '#93bc9e'
}
},
map: {
itemStyle: {
color: '#8aedd5'
},
areaStyle: {
color: '#93bc9e'
},
label: {
color: '#cef1db'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#93bc9e'],
[0.8, '#8aedd5'],
[1, '#a6d7c2']
],
width: 8
}
}
}
};
echarts.registerTheme('mint', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#8b1a2d',
'#a7314b',
'#e6004c',
'#ff8066',
'#8e5c4e',
'#ff1a66',
'#d6c582',
'#f0d4af'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#8b1a2d'
}
},
visualMap: {
color: ['#8b1a2d', '#a7314b']
},
toolbox: {
color: ['#8b1a2d', '#8b1a2d', '#8b1a2d', '#8b1a2d']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#8b1a2d',
type: 'dashed'
},
crossStyle: {
color: '#8b1a2d'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#8b1a2d' // Handle color
},
timeline: {
lineStyle: {
color: '#8b1a2d'
},
controlStyle: {
color: '#8b1a2d',
borderColor: '#8b1a2d'
}
},
candlestick: {
itemStyle: {
color: '#a7314b',
color0: '#d6c582'
},
lineStyle: {
width: 1,
color: '#8e5c4e',
color0: '#f0d4af'
},
areaStyle: {
color: '#8b1a2d',
color0: '#ff8066'
}
},
map: {
itemStyle: {
color: '#8b1a2d'
},
areaStyle: {
color: '#ff8066'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#ff8066'
},
linkStyle: {
color: '#8b1a2d'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#a7314b'],
[0.8, '#8b1a2d'],
[1, '#8e5c4e']
],
width: 8
}
}
}
};
echarts.registerTheme('red-velvet', theme);
});
@@ -1,225 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#d8361b',
'#f16b4c',
'#f7b4a9',
'#d26666',
'#99311c',
'#c42703',
'#d07e75'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#d8361b'
}
},
visualMap: {
color: ['#d8361b', '#ffd2d2']
},
dataRange: {
color: ['#bd0707', '#ffd2d2']
},
toolbox: {
color: ['#d8361b', '#d8361b', '#d8361b', '#d8361b']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#d8361b',
type: 'dashed'
},
crossStyle: {
color: '#d8361b'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(216,54,27,0.2)', // Fill the color
handleColor: '#d8361b' // Handle color
},
grid: {
borderWidth: 0
},
categoryAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#d8361b'
}
},
splitLine: {
// Separation line
lineStyle: {
// Property 'lineStyle' (see lineStyle) controls line styles
color: ['#eee']
}
}
},
valueAxis: {
axisLine: {
// Coordinate axis
lineStyle: {
// Property 'lineStyle' controls line styles
color: '#d8361b'
}
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(250,250,250,0.1)', 'rgba(200,200,200,0.1)']
}
},
splitLine: {
// Separation line
lineStyle: {
// Property 'lineStyle' (see lineStyle) controls line styles
color: ['#eee']
}
}
},
timeline: {
lineStyle: {
color: '#d8361b'
},
controlStyle: {
color: '#d8361b',
borderColor: '#d8361b'
}
},
candlestick: {
itemStyle: {
color: '#f16b4c',
color0: '#f7b4a9'
},
lineStyle: {
width: 1,
color: '#d8361b',
color0: '#d26666'
},
areaStyle: {
color: '#d8361b',
color0: '#d07e75'
}
},
graph: {
itemStyle: {
color: '#d07e75'
},
linkStyle: {
color: '#d8361b'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#d07e75',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#d8361b'
}
},
map: {
itemStyle: {
color: '#d8361b'
},
areaStyle: {
color: '#d07e75'
},
label: {
color: '#c12e34'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#f16b4c'],
[0.8, '#d8361b'],
[1, '#99311c']
],
width: 8
}
}
}
};
echarts.registerTheme('red', theme);
});
@@ -1,119 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#E01F54',
'#001852',
'#f5e8c8',
'#b8d2c7',
'#c6b38e',
'#a4d8c2',
'#f3d999',
'#d3758f',
'#dcc392',
'#2e4783',
'#82b6e9',
'#ff6347',
'#a092f1',
'#0a915d',
'#eaf889',
'#6699FF',
'#ff6666',
'#3cb371',
'#d5b158',
'#38b6b6'
];
var theme = {
color: colorPalette,
visualMap: {
color: ['#e01f54', '#e7dbc3'],
textStyle: {
color: '#333'
}
},
candlestick: {
itemStyle: {
color: '#e01f54',
color0: '#001852'
},
lineStyle: {
width: 1,
color: '#f5e8c8',
color0: '#b8d2c7'
},
areaStyle: {
color: '#a4d8c2',
color0: '#f3d999'
}
},
graph: {
itemStyle: {
color: '#a4d8c2'
},
linkStyle: {
color: '#f3d999'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#E01F54'],
[0.8, '#b8d2c7'],
[1, '#001852']
],
width: 8
}
}
}
};
echarts.registerTheme('roma', theme);
});
@@ -1,163 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#3f7ea6',
'#993366',
'#408000',
'#8c6f56',
'#a65149',
'#731f17',
'#adc2eb',
'#d9c3b0'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#3f7ea6'
}
},
visualMap: {
color: ['#3f7ea6', '#993366']
},
toolbox: {
color: ['#3f7ea6', '#3f7ea6', '#3f7ea6', '#3f7ea6']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#3f7ea6',
type: 'dashed'
},
crossStyle: {
color: '#3f7ea6'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(200,200,200,0.2)', // Fill the color
handleColor: '#3f7ea6' // Handle color
},
timeline: {
lineStyle: {
color: '#3f7ea6'
},
controlStyle: {
color: '#3f7ea6',
borderColor: '#3f7ea6'
}
},
candlestick: {
itemStyle: {
color: '#d9c3b0',
color0: '#8c6f56'
},
lineStyle: {
width: 1,
color: '#731f17',
color0: '#a65149'
},
areaStyle: {
color: '#3f7ea6',
color0: '#993366'
}
},
map: {
itemStyle: {
color: '#d9c3b0'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
graph: {
itemStyle: {
color: '#993366'
},
linkStyle: {
color: '#3f7ea6'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#d9c3b0'],
[0.8, '#3f7ea6'],
[1, '#731f17']
],
width: 8
}
}
}
};
echarts.registerTheme('royal', theme);
});
@@ -1,140 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#e52c3c',
'#f7b1ab',
'#fa506c',
'#f59288',
'#f8c4d8',
'#e54f5c',
'#f06d5c',
'#e54f80',
'#f29c9f',
'#eeb5b7'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#e52c3c'
}
},
visualMap: {
color: ['#e52c3c', '#f7b1ab']
},
dataRange: {
color: ['#e52c3c', '#f7b1ab']
},
candlestick: {
itemStyle: {
color: '#e52c3c',
color0: '#f59288'
},
lineStyle: {
width: 1,
color: '#e52c3c',
color0: '#f59288'
},
areaStyle: {
color: '#fa506c',
color0: '#f8c4d8'
}
},
map: {
itemStyle: {
color: '#e52c3c',
borderColor: '#fff',
borderWidth: 1
},
areaStyle: {
color: '#ccc'
},
label: {
color: 'rgba(139,69,19,1)',
show: false
}
},
graph: {
itemStyle: {
color: '#f2385a'
},
nodeStyle: {
brushType: 'both',
strokeColor: '#e54f5c'
},
linkStyle: {
color: '#f2385a',
strokeColor: '#e54f5c'
},
label: {
color: '#f2385a',
show: false
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#e52c3c'],
[0.8, '#f7b1ab'],
[1, '#fa506c']
],
width: 8
}
}
}
};
echarts.registerTheme('sakura', theme);
});
@@ -1,175 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#c12e34',
'#e6b600',
'#0098d9',
'#2b821d',
'#005eaa',
'#339ca8',
'#cda819',
'#32a487'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal'
}
},
visualMap: {
color: ['#1790cf', '#a2d4e6']
},
toolbox: {
iconStyle: {
borderColor: '#06467c'
}
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.6)'
},
dataZoom: {
dataBackgroundColor: '#dedede',
fillerColor: 'rgba(154,217,247,0.2)',
handleColor: '#005eaa'
},
timeline: {
lineStyle: {
color: '#005eaa'
},
controlStyle: {
color: '#005eaa',
borderColor: '#005eaa'
}
},
candlestick: {
itemStyle: {
color: '#c12e34',
color0: '#2b821d'
},
lineStyle: {
width: 1,
color: '#c12e34',
color0: '#2b821d'
},
areaStyle: {
color: '#e6b600',
color0: '#005eaa'
}
},
graph: {
itemStyle: {
color: '#e6b600'
},
linkStyle: {
color: '#005eaa'
}
},
map: {
itemStyle: {
color: '#f2385a',
borderColor: '#eee',
areaColor: '#ddd'
},
areaStyle: {
color: '#ddd'
},
label: {
color: '#c12e34'
}
},
gauge: {
axisLine: {
show: true,
lineStyle: {
color: [
[0.2, '#2b821d'],
[0.8, '#005eaa'],
[1, '#c12e34']
],
width: 5
}
},
axisTick: {
splitNumber: 10,
length: 8,
lineStyle: {
color: 'auto'
}
},
axisLabel: {
color: 'auto'
},
splitLine: {
length: 12,
lineStyle: {
color: 'auto'
}
},
pointer: {
length: '90%',
width: 3,
color: 'auto'
},
title: {
color: '#333'
},
detail: {
color: 'auto'
}
}
};
echarts.registerTheme('shine', theme);
});
@@ -1,184 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#4d4d4d',
'#3a5897',
'#007bb6',
'#7094db',
'#0080ff',
'#b3b3ff',
'#00bdec',
'#33ccff',
'#ccddff',
'#eeeeee'
];
var theme = {
color: colorPalette,
title: {
textStyle: {
fontWeight: 'normal',
color: '#00aecd'
}
},
visualMap: {
color: ['#00aecd', '#a2d4e6']
},
toolbox: {
color: ['#00aecd', '#00aecd', '#00aecd', '#00aecd']
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.5)',
axisPointer: {
// Axis indicator, coordinate trigger effective
type: 'line', // The default is a straight line 'line' | 'shadow'
lineStyle: {
// Straight line indicator style settings
color: '#00aecd',
type: 'dashed'
},
crossStyle: {
color: '#00aecd'
},
shadowStyle: {
// Shadow indicator style settings
color: 'rgba(200,200,200,0.3)'
}
}
},
// Area scaling controller
dataZoom: {
dataBackgroundColor: '#eee', // Data background color
fillerColor: 'rgba(144,197,237,0.2)', // Fill the color
handleColor: '#00aecd' // Handle color
},
timeline: {
lineStyle: {
color: '#00aecd'
},
controlStyle: {
color: '#00aecd',
},
emphasis: {
controlStyle: {
color: '#00aecd'
}
}
},
candlestick: {
itemStyle: {
color: '#ddd',
color0: '#eee'
},
lineStyle: {
width: 1,
color: '#33ccff',
color0: '#1bb4cf'
},
areaStyle: {
color: '#7094db',
color0: '#33ccff'
}
},
chord: {
padding: 4,
itemStyle: {
color: '#7094db',
borderWidth: 1,
borderColor: 'rgba(128, 128, 128, 0.5)'
},
lineStyle: {
color: 'rgba(128, 128, 128, 0.5)'
},
areaStyle: {
color: '#33ccff'
}
},
graph: {
itemStyle: {
color: '#7094db'
},
linkStyle: {
color: '#33ccff'
}
},
map: {
itemStyle: {
color: '#7094db'
},
areaStyle: {
color: '#33ccff'
},
label: {
color: '#ddd'
}
},
gauge: {
axisLine: {
lineStyle: {
color: [
[0.2, '#dddddd'],
[0.8, '#00aecd'],
[1, '#33ccff']
],
width: 8
}
}
}
};
echarts.registerTheme('tech-blue', theme);
});
@@ -1,88 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default {
title: {
text: 'Area Chart',
left: 'center',
top: '3%',
textStyle: {
fontWeight: 'normal'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '12%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']
},
yAxis: {
type: 'value',
splitNumber: 3
},
dataZoom: {
},
series: [
{
name:'Email',
type:'line',
stack: '总量',
areaStyle: {},
data:[120, 132, 101, 134, 90, 230, 210]
},
{
name:'联盟广告',
type:'line',
stack: '总量',
areaStyle: {},
data:[220, 182, 191, 234, 290, 330, 310]
},
{
name:'视频广告',
type:'line',
stack: '总量',
areaStyle: {},
data:[150, 232, 201, 154, 190, 330, 410]
},
{
name:'直接访问',
type:'line',
stack: '总量',
areaStyle: {},
data:[320, 332, 301, 334, 390, 330, 320]
},
{
name:'搜索引擎',
type:'line',
stack: '总量',
label: {
show: true,
position: 'top'
},
areaStyle: {},
data:[820, 932, 901, 934, 1290, 1330, 1320]
}
]
};
@@ -1,107 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default {
title: {
text: 'Bar Chart',
left: 'center',
top: '3%',
textStyle: {
fontWeight: 'normal'
}
},
toolbox: {
top: '3%',
feature: {
magicType: {
type: ['line', 'bar', 'stack', 'tiled']
},
restore: {},
dataZoom: {},
saveAsImage: {}
}
},
grid: {
left: '13%',
right: '5%',
bottom: '5%',
textStyle: {
fontWeight: 'normal'
}
},
xAxis: {
type: 'value'
},
yAxis: {
type: 'category',
data: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday']
},
series: [
{
name:'直接访问',
type:'bar',
stack: '总量',
label: {
show: true,
position: 'insideRight'
},
data:[320, 302, 301, 334, 390, 330, 320]
},
{
name:'邮件营销',
type:'bar',
stack: '总量',
label: {
show: true,
position: 'insideRight'
},
data:[120, 132, 101, 134, 90, 230, 210]
},
{
name:'联盟广告',
type:'bar',
stack: '总量',
label: {
show: true,
position: 'insideRight'
},
data:[220, 182, 191, 234, 290, 330, 310]
},
{
name:'视频广告',
type:'bar',
stack: '总量',
label: {
show: true,
position: 'insideRight'
},
data:[150, 212, 201, 154, 190, 330, 410]
},
{
name:'搜索引擎',
type:'bar',
stack: '总量',
label: {
show: true,
position: 'insideRight'
},
data:[820, 832, 901, 934, 1290, 1330, 1320]
}
]
};
File diff suppressed because it is too large Load Diff
@@ -1,141 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default {
visualMap: {
show: true,
min: 0,
max: 1500,
right: 50,
top: 'middle',
text:['高','低']
// orient: 'horizontal'
},
selectedMode: 'single',
series : [
{
name: 'iphone3',
type: 'map',
map: 'china',
showLegendSymbol: true,
label: {
show: false,
},
emphasis: {
label: {
show: false,
}
},
data:[
{name: '北京',value: 500},
{name: '天津',value: 500},
{name: '上海',value: 500},
{name: '重庆',value: 500},
{name: '河北',value: 500},
{name: '河南',value: 500},
{name: '云南',value: 500},
{name: '辽宁',value: 500},
{name: '黑龙江',value: 500},
{name: '湖南',value: 500},
{name: '安徽',value: 500},
{name: '山东',value: 500},
{name: '新疆',value: 500},
{name: '江苏',value: 500},
{name: '浙江',value: 500},
{name: '江西',value: 500},
{name: '湖北',value: 500},
{name: '广西',value: 500},
{name: '甘肃',value: 500},
{name: '山西',value: 500},
{name: '内蒙古',value: 500},
{name: '陕西',value: 500},
{name: '吉林',value: 500},
{name: '福建',value: 500},
{name: '贵州',value: 500},
{name: '广东',value: 500},
{name: '青海',value: 500},
{name: '西藏',value: 500},
{name: '四川',value: 500},
{name: '宁夏',value: 500},
{name: '海南',value: 500},
{name: '台湾',value: 500},
{name: '香港',value: 500},
{name: '澳门',value: 500}
]
},
{
name: 'iphone4',
type: 'map',
mapType: 'china',
showLegendSymbol: true,
label: {
show: false,
},
emphasis: {
label: {
show: false
}
},
data:[
{name: '北京',value: 500},
{name: '天津',value: 500},
{name: '上海',value: 500},
{name: '重庆',value: 500},
{name: '河北',value: 500},
{name: '安徽',value: 500},
{name: '新疆',value: 500},
{name: '浙江',value: 500},
{name: '江西',value: 500},
{name: '山西',value: 500},
{name: '内蒙古',value: 500},
{name: '吉林',value: 500},
{name: '福建',value: 500},
{name: '广东',value: 500},
{name: '西藏',value: 500},
{name: '四川',value: 500},
{name: '宁夏',value: 500},
{name: '香港',value: 500},
{name: '澳门',value: 500}
]
},
{
name: 'iphone5',
type: 'map',
mapType: 'china',
showLegendSymbol: true,
label: {
show: false,
},
emphasis: {
label: {
show: false
}
},
data:[
{name: '北京',value: 500},
{name: '天津',value: 500},
{name: '上海',value: 500},
{name: '广东',value: 500},
{name: '台湾',value: 500},
{name: '香港',value: 500},
{name: '澳门',value: 500}
]
}
]
};
@@ -1,87 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default {
legend: {
bottom: '5%',
data: ['rose1', 'rose2', 'rose3', 'rose4']
},
series : [
{
name:'半径模式',
type:'pie',
radius : [20, 80],
center : ['25%', 110],
label: {
show: false,
},
lableLine: {
show: false,
},
emphasis: {
label: {
show: true
},
lableLine: {
show: true
}
},
data:[
{value:10, name:'rose1'},
{value:5, name:'rose2'},
{value:15, name:'rose3'},
{value:25, name:'rose4'},
{value:20, name:'rose5'},
{value:35, name:'rose6'},
{value:30, name:'rose7'},
{value:40, name:'rose8'}
]
},
{
name:'面积模式',
type:'pie',
radius : [30, 80],
center : ['75%', 110],
roseType : 'area',
labelLine: {
length: 5
},
data:[
{value:10, name:'rose1'},
{value:5, name:'rose2'},
{value:15, name:'rose3'},
{value:25, name:'rose4'},
{value:20, name:'rose5'},
{value:35, name:'rose6'},
{value:30, name:'rose7'},
{value:40, name:'rose8'}
]
},
{
name:'仪表盘',
type:'gauge',
radius : 100,
center : ['50%', 280],
detail : {formatter:'{value}%'},
data:[
{value:50, name:'Gauge'}
]
}
]
};
@@ -1,201 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export default {
timeline: {
left: '2%',
right: '2%',
data: [
'2002-01-01','2003-01-01','2004-01-01',
{
value: '2005-01-01',
symbol: 'diamond',
symbolSize: 16
},
'2006-01-01', '2007-01-01','2008-01-01','2009-01-01','2010-01-01',
{
value: '2011-01-01',
symbol: 'diamond',
symbolSize: 18
}
],
label: {
formatter : function(s) {
return (new Date(s)).getFullYear();
}
}
},
options: [{
grid: {
left: '13%',
right: '5%',
bottom: '20%'
},
xAxis: {
type : 'value',
scale:true,
axisLabel : {
formatter: '{value} cm'
}
},
yAxis: {
type : 'value',
scale:true,
axisLabel : {
formatter: '{value} kg'
}
},
series : [
{
name:'女性',
type:'scatter',
data: [[161.2, 51.6], [167.5, 59.0], [159.5, 49.2], [157.0, 63.0], [155.8, 53.6],
[170.0, 59.0], [159.1, 47.6], [166.0, 69.8], [176.2, 66.8], [160.2, 75.2],
[172.5, 55.2], [170.9, 54.2], [172.9, 62.5], [153.4, 42.0], [160.0, 50.0],
[147.2, 49.8], [168.2, 49.2], [175.0, 73.2], [157.0, 47.8], [167.6, 68.8],
[159.5, 50.6], [175.0, 82.5], [166.8, 57.2], [176.5, 87.8], [170.2, 72.8],
[174.0, 54.5], [173.0, 59.8], [179.9, 67.3], [170.5, 67.8], [160.0, 47.0],
[154.4, 46.2], [162.0, 55.0], [176.5, 83.0], [160.0, 54.4], [152.0, 45.8],
[162.1, 53.6], [170.0, 73.2], [160.2, 52.1], [161.3, 67.9], [166.4, 56.6],
[168.9, 62.3], [163.8, 58.5], [167.6, 54.5], [160.0, 50.2], [161.3, 60.3],
[167.6, 58.3], [165.1, 56.2], [160.0, 50.2], [170.0, 72.9], [157.5, 59.8],
[167.6, 61.0], [160.7, 69.1], [163.2, 55.9], [152.4, 46.5], [157.5, 54.3],
[168.3, 54.8], [180.3, 60.7], [165.5, 60.0], [165.0, 62.0], [164.5, 60.3],
[156.0, 52.7], [160.0, 74.3], [163.0, 62.0], [165.7, 73.1], [161.0, 80.0],
[162.0, 54.7], [166.0, 53.2], [174.0, 75.7], [172.7, 61.1], [167.6, 55.7],
[151.1, 48.7], [164.5, 52.3], [163.5, 50.0], [152.0, 59.3], [169.0, 62.5],
[164.0, 55.7], [161.2, 54.8], [155.0, 45.9], [170.0, 70.6], [176.2, 67.2],
[170.0, 69.4], [162.5, 58.2], [170.3, 64.8], [164.1, 71.6], [169.5, 52.8],
[163.2, 59.8], [154.5, 49.0], [159.8, 50.0], [173.2, 69.2], [170.0, 55.9],
[161.4, 63.4], [169.0, 58.2], [166.2, 58.6], [159.4, 45.7], [162.5, 52.2],
[159.0, 48.6], [162.8, 57.8], [159.0, 55.6], [179.8, 66.8], [162.9, 59.4],
[161.0, 53.6], [151.1, 73.2], [168.2, 53.4], [168.9, 69.0], [173.2, 58.4],
[171.8, 56.2], [178.0, 70.6], [164.3, 59.8], [163.0, 72.0], [168.5, 65.2],
[166.8, 56.6], [172.7, 105.2], [163.5, 51.8], [169.4, 63.4], [167.8, 59.0],
[159.5, 47.6], [167.6, 63.0], [161.2, 55.2], [160.0, 45.0], [163.2, 54.0],
[162.2, 50.2], [161.3, 60.2], [149.5, 44.8], [157.5, 58.8], [163.2, 56.4],
[172.7, 62.0], [155.0, 49.2], [156.5, 67.2], [164.0, 53.8], [160.9, 54.4],
[162.8, 58.0], [167.0, 59.8], [160.0, 54.8], [160.0, 43.2], [168.9, 60.5],
[158.2, 46.4], [156.0, 64.4], [160.0, 48.8], [167.1, 62.2], [158.0, 55.5],
[167.6, 57.8], [156.0, 54.6], [162.1, 59.2], [173.4, 52.7], [159.8, 53.2],
[170.5, 64.5], [159.2, 51.8], [157.5, 56.0], [161.3, 63.6], [162.6, 63.2],
[160.0, 59.5], [168.9, 56.8], [165.1, 64.1], [162.6, 50.0], [165.1, 72.3],
[166.4, 55.0], [160.0, 55.9], [152.4, 60.4], [170.2, 69.1], [162.6, 84.5],
[170.2, 55.9], [158.8, 55.5], [172.7, 69.5], [167.6, 76.4], [162.6, 61.4],
[167.6, 65.9], [156.2, 58.6], [175.2, 66.8], [172.1, 56.6], [162.6, 58.6],
[160.0, 55.9], [165.1, 59.1], [182.9, 81.8], [166.4, 70.7], [165.1, 56.8],
[177.8, 60.0], [165.1, 58.2], [175.3, 72.7], [154.9, 54.1], [158.8, 49.1],
[172.7, 75.9], [168.9, 55.0], [161.3, 57.3], [167.6, 55.0], [165.1, 65.5],
[175.3, 65.5], [157.5, 48.6], [163.8, 58.6], [167.6, 63.6], [165.1, 55.2],
[165.1, 62.7], [168.9, 56.6], [162.6, 53.9], [164.5, 63.2], [176.5, 73.6],
[168.9, 62.0], [175.3, 63.6], [159.4, 53.2], [160.0, 53.4], [170.2, 55.0],
[162.6, 70.5], [167.6, 54.5], [162.6, 54.5], [160.7, 55.9], [160.0, 59.0],
[157.5, 63.6], [162.6, 54.5], [152.4, 47.3], [170.2, 67.7], [165.1, 80.9],
[172.7, 70.5], [165.1, 60.9], [170.2, 63.6], [170.2, 54.5], [170.2, 59.1],
[161.3, 70.5], [167.6, 52.7], [167.6, 62.7], [165.1, 86.3], [162.6, 66.4],
[152.4, 67.3], [168.9, 63.0], [170.2, 73.6], [175.2, 62.3], [175.2, 57.7],
[160.0, 55.4], [165.1, 104.1], [174.0, 55.5], [170.2, 77.3], [160.0, 80.5],
[167.6, 64.5], [167.6, 72.3], [167.6, 61.4], [154.9, 58.2], [162.6, 81.8],
[175.3, 63.6], [171.4, 53.4], [157.5, 54.5], [165.1, 53.6], [160.0, 60.0],
[174.0, 73.6], [162.6, 61.4], [174.0, 55.5], [162.6, 63.6], [161.3, 60.9],
[156.2, 60.0], [149.9, 46.8], [169.5, 57.3], [160.0, 64.1], [175.3, 63.6],
[169.5, 67.3], [160.0, 75.5], [172.7, 68.2], [162.6, 61.4], [157.5, 76.8],
[176.5, 71.8], [164.4, 55.5], [160.7, 48.6], [174.0, 66.4], [163.8, 67.3]
],
markPoint : {
data : [
{type : 'max', name: '最大值'},
{type : 'min', name: '最小值'}
]
},
markLine : {
data : [
{type : 'average', name: '平均值'}
]
}
},
{
name:'男性',
type:'scatter',
data: [[174.0, 65.6], [175.3, 71.8], [193.5, 80.7], [186.5, 72.6], [187.2, 78.8],
[181.5, 74.8], [184.0, 86.4], [184.5, 78.4], [175.0, 62.0], [184.0, 81.6],
[180.0, 76.6], [177.8, 83.6], [192.0, 90.0], [176.0, 74.6], [174.0, 71.0],
[184.0, 79.6], [192.7, 93.8], [171.5, 70.0], [173.0, 72.4], [176.0, 85.9],
[176.0, 78.8], [180.5, 77.8], [172.7, 66.2], [176.0, 86.4], [173.5, 81.8],
[178.0, 89.6], [180.3, 82.8], [180.3, 76.4], [164.5, 63.2], [173.0, 60.9],
[183.5, 74.8], [175.5, 70.0], [188.0, 72.4], [189.2, 84.1], [172.8, 69.1],
[170.0, 59.5], [182.0, 67.2], [170.0, 61.3], [177.8, 68.6], [184.2, 80.1],
[186.7, 87.8], [171.4, 84.7], [172.7, 73.4], [175.3, 72.1], [180.3, 82.6],
[182.9, 88.7], [188.0, 84.1], [177.2, 94.1], [172.1, 74.9], [167.0, 59.1],
[169.5, 75.6], [174.0, 86.2], [172.7, 75.3], [182.2, 87.1], [164.1, 55.2],
[163.0, 57.0], [171.5, 61.4], [184.2, 76.8], [174.0, 86.8], [174.0, 72.2],
[177.0, 71.6], [186.0, 84.8], [167.0, 68.2], [171.8, 66.1], [182.0, 72.0],
[167.0, 64.6], [177.8, 74.8], [164.5, 70.0], [192.0, 101.6], [175.5, 63.2],
[171.2, 79.1], [181.6, 78.9], [167.4, 67.7], [181.1, 66.0], [177.0, 68.2],
[174.5, 63.9], [177.5, 72.0], [170.5, 56.8], [182.4, 74.5], [197.1, 90.9],
[180.1, 93.0], [175.5, 80.9], [180.6, 72.7], [184.4, 68.0], [175.5, 70.9],
[180.6, 72.5], [177.0, 72.5], [177.1, 83.4], [181.6, 75.5], [176.5, 73.0],
[175.0, 70.2], [174.0, 73.4], [165.1, 70.5], [177.0, 68.9], [192.0, 102.3],
[176.5, 68.4], [169.4, 65.9], [182.1, 75.7], [179.8, 84.5], [175.3, 87.7],
[184.9, 86.4], [177.3, 73.2], [167.4, 53.9], [178.1, 72.0], [168.9, 55.5],
[157.2, 58.4], [180.3, 83.2], [170.2, 72.7], [177.8, 64.1], [172.7, 72.3],
[165.1, 65.0], [186.7, 86.4], [165.1, 65.0], [174.0, 88.6], [175.3, 84.1],
[185.4, 66.8], [177.8, 75.5], [180.3, 93.2], [180.3, 82.7], [177.8, 58.0],
[177.8, 79.5], [177.8, 78.6], [177.8, 71.8], [177.8, 116.4], [163.8, 72.2],
[188.0, 83.6], [198.1, 85.5], [175.3, 90.9], [166.4, 85.9], [190.5, 89.1],
[166.4, 75.0], [177.8, 77.7], [179.7, 86.4], [172.7, 90.9], [190.5, 73.6],
[185.4, 76.4], [168.9, 69.1], [167.6, 84.5], [175.3, 64.5], [170.2, 69.1],
[190.5, 108.6], [177.8, 86.4], [190.5, 80.9], [177.8, 87.7], [184.2, 94.5],
[176.5, 80.2], [177.8, 72.0], [180.3, 71.4], [171.4, 72.7], [172.7, 84.1],
[172.7, 76.8], [177.8, 63.6], [177.8, 80.9], [182.9, 80.9], [170.2, 85.5],
[167.6, 68.6], [175.3, 67.7], [165.1, 66.4], [185.4, 102.3], [181.6, 70.5],
[172.7, 95.9], [190.5, 84.1], [179.1, 87.3], [175.3, 71.8], [170.2, 65.9],
[193.0, 95.9], [171.4, 91.4], [177.8, 81.8], [177.8, 96.8], [167.6, 69.1],
[167.6, 82.7], [180.3, 75.5], [182.9, 79.5], [176.5, 73.6], [186.7, 91.8],
[188.0, 84.1], [188.0, 85.9], [177.8, 81.8], [174.0, 82.5], [177.8, 80.5],
[171.4, 70.0], [185.4, 81.8], [185.4, 84.1], [188.0, 90.5], [188.0, 91.4],
[182.9, 89.1], [176.5, 85.0], [175.3, 69.1], [175.3, 73.6], [188.0, 80.5],
[188.0, 82.7], [175.3, 86.4], [170.5, 67.7], [179.1, 92.7], [177.8, 93.6],
[175.3, 70.9], [182.9, 75.0], [170.8, 93.2], [188.0, 93.2], [180.3, 77.7],
[177.8, 61.4], [185.4, 94.1], [168.9, 75.0], [185.4, 83.6], [180.3, 85.5],
[174.0, 73.9], [167.6, 66.8], [182.9, 87.3], [160.0, 72.3], [180.3, 88.6],
[167.6, 75.5], [186.7, 101.4], [175.3, 91.1], [175.3, 67.3], [175.9, 77.7],
[175.3, 81.8], [179.1, 75.5], [181.6, 84.5], [177.8, 76.6], [182.9, 85.0],
[177.8, 102.5], [184.2, 77.3], [179.1, 71.8], [176.5, 87.9], [188.0, 94.3],
[174.0, 70.9], [167.6, 64.5], [170.2, 77.3], [167.6, 72.3], [188.0, 87.3],
[174.0, 80.0], [176.5, 82.3], [180.3, 73.6], [167.6, 74.1], [188.0, 85.9],
[180.3, 73.2], [167.6, 76.3], [183.0, 65.9], [183.0, 90.9], [179.1, 89.1],
[170.2, 62.3], [177.8, 82.7], [179.1, 79.1], [190.5, 98.2], [177.8, 84.1],
[180.3, 83.2], [180.3, 83.2]
],
markPoint : {
data : [
{type : 'max', name: '最大值'},
{type : 'min', name: '最小值'}
]
},
markLine : {
data : [
{type : 'average', name: '平均值'}
]
}
}
]
}]
};
@@ -1,65 +0,0 @@
<!DOCTYPE html>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<script src="../../dist/echarts.js"></script>
<script src="../../map/js/china.js"></script>
</head>
<body>
<style>
body {
margin: 0;
}
</style>
<div id="main"></div>
<script type="module">
import bar from './option/bar';
import area from './option/area';
import scatter from './option/scatter';
import pie from './option/pie';
import graph from './option/graph';
import map from './option/map';
let options = [bar, area, scatter, pie, graph, map];
let mainDiv = document.querySelector('#main');
let theme = window.location.hash.slice(1);
let scriptTag = document.createElement('script');
scriptTag.src = '../' + theme + '.js';
scriptTag.onload = function () {
options.forEach(option => {
let div = document.createElement('div');
mainDiv.appendChild(div);
div.style.cssText = 'float:left;width:50%;height:400px';
let chart = echarts.init(div, theme);
option.animation = false;
chart.setOption(option);
});
}
document.body.appendChild(scriptTag);
</script>
</body>
</html>
@@ -1,63 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const glob = require('glob');
const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
async function wait(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
async function snapshot(browser, themePath) {
let themeName = path.basename(themePath, '.js');
let code = fs.readFileSync(themePath, 'utf-8');
let page = await browser.newPage();
await page.evaluateOnNewDocument(code);
await page.setViewport({ width: 1200, height: 1200 });
try {
await page.goto('http://localhost/echarts/theme/tool/thumb.html#' + themeName);
await wait(200);
await page.screenshot({ path: __dirname + '/../thumb/' + themeName + '.png' });
}
catch (e) {
console.log(e);
}
await page.close();
console.log('Updated ' + themeName);
}
glob('../*.js', async function (err, themePathList) {
let browser = await puppeteer.launch();
for (let themePath of themePathList) {
try {
await snapshot(browser, themePath);
}
catch(e) {
console.log(e);
}
}
await browser.close();
});
@@ -1,63 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS
factory(exports, require('echarts/lib/echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
})(this, function(exports, echarts) {
var log = function(msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
var colorPalette = [
'#d87c7c',
'#919e8b',
'#d7ab82',
'#6e7074',
'#61a0a8',
'#efa18d',
'#787464',
'#cc7e63',
'#724e58',
'#4b565b'
];
echarts.registerTheme('vintage', {
color: colorPalette,
backgroundColor: '#fef8ef',
graph: {
color: colorPalette
}
});
});
@@ -20,14 +20,14 @@
<nav class="service-nav"> <nav class="service-nav">
<!-- All APIs --> <!-- All APIs -->
<a class="service-nav__item" <a class="service-nav__item"
th:classappend="${selected == '-1' || #request.getParameter('selected') == '-1'} ? 'service-nav__item--active' : ''" th:classappend="${selected == '-1' or (param.selected != null and param.selected[0] == '-1')} ? 'service-nav__item--active' : ''"
th:href="@{/apis}"> th:href="@{/apis}">
전체 전체
</a> </a>
<!-- Service Categories --> <!-- Service Categories -->
<a class="service-nav__item" th:each="service : ${services}" <a class="service-nav__item" th:each="service : ${services}"
th:classappend="${(#request.getParameter('selected') != null ? #request.getParameter('selected') : (selected != null ? selected : apiSpecInfo.service)) == service.id} ? 'service-nav__item--active' : ''" th:classappend="${(param.selected != null ? param.selected[0] : (selected != null ? selected : apiSpecInfo.service)) == service.id} ? 'service-nav__item--active' : ''"
th:href="@{/apis(groupIds=${service.id})}"> th:href="@{/apis(groupIds=${service.id})}">
[[${service.groupName}]] [[${service.groupName}]]
</a> </a>
@@ -200,6 +200,20 @@
}); });
}); });
// 사용자 지정 Secret 조회
var btnRevealUserSecret = document.getElementById('btnRevealUserSecret');
if (btnRevealUserSecret) btnRevealUserSecret.addEventListener('click', function () {
openModal('사용자 지정 Secret 조회', '비밀번호 확인 후 값을 표시합니다.', function (pw) {
post('/webhook/verify-secret', pw).then(function (res) {
if (handleForceLogout(res)) { return; }
if (res.success) {
document.getElementById('userSecretMasked').textContent = res.userSecret || '';
closeModal();
} else { showError(res.message || '실패했습니다.'); }
}).catch(function () { showError('요청 처리 중 오류가 발생했습니다.'); });
});
});
// 서명검증키 재발급 // 서명검증키 재발급
var btnRegen = document.getElementById('btnRegenSecret'); var btnRegen = document.getElementById('btnRegenSecret');
if (btnRegen) btnRegen.addEventListener('click', function () { if (btnRegen) btnRegen.addEventListener('click', function () {
@@ -129,8 +129,14 @@
<!--/* LiveReload (개발 전용): DevTools 가 정적 리소스/템플릿 변경을 감지하면 브라우저를 자동 새로고침한다. <!--/* LiveReload (개발 전용): DevTools 가 정적 리소스/템플릿 변경을 감지하면 브라우저를 자동 새로고침한다.
localhost 외 IP(예: 172.30.1.14)로 접속해도 동작하도록 접속 호스트 기준으로 livereload.js 를 로드한다. localhost 외 IP(예: 172.30.1.14)로 접속해도 동작하도록 접속 호스트 기준으로 livereload.js 를 로드한다.
prod/stage 에는 DevTools 자체가 없으므로(developmentOnly) 개발 프로파일에서만 주입한다. */--> prod/stage 에는 DevTools 자체가 없으므로(developmentOnly) 개발 프로파일에서만 주입한다. */-->
<script th:if="${@environment.acceptsProfiles('local_rinjaemac','local')}" <script th:if="${@environment.acceptsProfiles('local_rinjaemac','local')}">
th:src="|//${#request.serverName}:35729/livereload.js|"></script> // Thymeleaf 3.1 은 #request 표현식 객체를 제거했으므로 호스트는 브라우저에서 읽는다.
(function () {
var s = document.createElement('script');
s.src = '//' + location.hostname + ':35729/livereload.js';
document.head.appendChild(s);
})();
</script>
</head> </head>
</html> </html>
@@ -0,0 +1,107 @@
package com.eactive.apim.portal.common.compatibility;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import com.eactive.apim.portal.common.util.UserTypeUtil;
import com.eactive.apim.portal.common.validator.CellPhoneValidator;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* Commons Lang 업그레이드 시 포털에서 사용하는 문자열 처리 결과가 유지되는지 검증한다.
*/
class CommonsLangUpgradeCompatibilityTest {
@AfterEach
void resetUserTypeConfiguration() {
UserTypeUtil.setInternalEmailDomains(null);
}
@Test
@DisplayName("빈 문자열과 공백 판정이 기존 분기 조건을 유지한다")
void keepsEmptyAndBlankBranchingBehavior() {
assertAll(
() -> assertTrue(StringUtils.isEmpty(null)),
() -> assertTrue(StringUtils.isEmpty("")),
() -> assertFalse(StringUtils.isEmpty(" ")),
() -> assertTrue(StringUtils.isBlank(" \t")),
() -> assertFalse(StringUtils.isNotBlank(" \t")),
() -> assertTrue(StringUtils.isNotEmpty(" "))
);
}
@Test
@DisplayName("폼 입력과 상태 설정의 trim 및 기본값 처리가 유지된다")
void keepsTrimmingAndDefaultingBehavior() {
assertAll(
() -> assertEquals("서비스명", StringUtils.trimToEmpty(" 서비스명 ")),
() -> assertEquals("", StringUtils.trimToEmpty(null)),
() -> assertEquals("api-001", StringUtils.defaultIfBlank(" ", "api-001")),
() -> assertEquals("API 이름", StringUtils.defaultIfBlank("API 이름", "api-001")),
() -> assertTrue(Strings.CI.equalsAny(" Y ".trim(), "true", "y", "1"))
);
}
@Test
@DisplayName("API 경로와 메시지 리소스명 문자열 처리가 유지된다")
void keepsPathAndResourceNameBehavior() {
String fullPath = StringUtils.join("/openapi/", "/", Strings.CS.removeStart("/users", "/"));
String normalizedPath = fullPath.replaceAll("//+", "/");
String resource = "/WEB-INF/messages/messages_ko.properties";
String baseName = StringUtils.substringBeforeLast(resource, ".properties");
assertAll(
() -> assertEquals("/openapi/users", normalizedPath),
() -> assertEquals("/openapi", Strings.CS.removeEnd("/openapi/", "/")),
() -> assertEquals("/WEB-INF/messages", StringUtils.substringBeforeLast(baseName, "/")),
() -> assertEquals("messages_ko", StringUtils.substringAfterLast(baseName, "/")),
() -> assertEquals("messages", StringUtils.substringBeforeLast("messages_ko", "_"))
);
}
@SuppressWarnings("deprecation")
@Test
@DisplayName("API와 약관 본문의 HTML 엔티티 디코딩 결과가 유지된다")
void keepsHtmlUnescapeBehavior() {
assertEquals("<p>이용약관 & 안내</p>",
StringEscapeUtils.unescapeHtml4("&lt;p&gt;이용약관 &amp; 안내&lt;/p&gt;"));
}
@Test
@DisplayName("휴대전화 검증의 기존 허용 및 거부 결과가 유지된다")
void keepsCellPhoneValidationBehavior() {
CellPhoneValidator validator = new CellPhoneValidator();
assertAll(
() -> assertTrue(validator.isValid("010-1234-5678", null)),
() -> assertTrue(validator.isValid("821012345678", null)),
() -> assertFalse(validator.isValid("", null)),
() -> assertFalse(validator.isValid("02-1234-5678", null)),
() -> assertFalse(validator.isValid("010-12-5678", null))
);
}
@Test
@DisplayName("내부 사용자 이메일 도메인 정규화와 판별 결과가 유지된다")
void keepsInternalUserDomainBehavior() {
UserTypeUtil.setInternalEmailDomains(Arrays.asList(" DJBANK.com ", "@Partner.COM", " "));
assertAll(
() -> assertEquals(Arrays.asList("@djbank.com", "@partner.com"),
UserTypeUtil.getInternalEmailDomains()),
() -> assertTrue(UserTypeUtil.isInternalEmail(" USER@DJBANK.COM ")),
() -> assertTrue(UserTypeUtil.isInternalEmail("user@partner.com")),
() -> assertFalse(UserTypeUtil.isInternalEmail("user@example.com")),
() -> assertFalse(UserTypeUtil.isInternalEmail(null))
);
}
}
@@ -0,0 +1,128 @@
package com.eactive.apim.portal.common.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.eactive.apim.portal.djb.apistatus.dto.DailyStatDTO;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
/**
* jackson 2.13.5 → 2.18.x 상향에 따른 JSON 직렬화 회귀 테스트.
* <p>
* Spring Boot 의 {@link JacksonAutoConfiguration} 이 만들어 주는 ObjectMapper(= MVC 응답에 실제로 쓰이는 것)로
* 화면/AJAX 응답의 날짜 표현이 그대로인지 고정한다. 직접 new 한 ObjectMapper 나 Jackson2ObjectMapperBuilder 는
* WRITE_DATES_AS_TIMESTAMPS 가 켜져 있어 실제 응답과 다르므로 쓰지 않는다.
* </p>
*/
class JacksonDateSerializationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class));
/** Boot 가 구성한 ObjectMapper 로 검증 로직을 실행한다. */
private void withBootObjectMapper(MapperAssertion assertion) {
contextRunner.run(context -> assertion.accept(context.getBean(ObjectMapper.class)));
}
@FunctionalInterface
interface MapperAssertion {
void accept(ObjectMapper objectMapper) throws Exception;
}
/** 테스트 소스에는 lombok annotation processor 가 걸려 있지 않아 접근자를 직접 둔다. */
public static class SampleDTO {
private LocalDateTime createdAt;
private LocalDate baseDate;
private String name;
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDate getBaseDate() {
return baseDate;
}
public void setBaseDate(LocalDate baseDate) {
this.baseDate = baseDate;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Test
@DisplayName("@JsonFormat 지정 필드는 지정 패턴 문자열로 직렬화된다")
void jsonFormatPatternIsHonored() {
withBootObjectMapper(objectMapper -> {
DailyStatDTO dto = new DailyStatDTO();
dto.setStatDate(LocalDate.of(2026, 8, 18));
dto.setUptimeRatio(0.9987);
dto.setStatus("NORMAL");
String json = objectMapper.writeValueAsString(dto);
assertTrue(json.contains("\"statDate\":\"2026-08-18\""), json);
assertTrue(json.contains("\"status\":\"NORMAL\""), json);
});
}
@Test
@DisplayName("어노테이션 없는 날짜 필드는 타임스탬프 숫자가 아니라 ISO-8601 문자열이다")
void plainDatesAreIsoStrings() {
withBootObjectMapper(objectMapper -> {
SampleDTO dto = new SampleDTO();
dto.setCreatedAt(LocalDateTime.of(2026, 8, 18, 9, 30, 0));
dto.setBaseDate(LocalDate.of(2026, 8, 18));
dto.setName("포털");
String json = objectMapper.writeValueAsString(dto);
assertTrue(json.contains("\"createdAt\":\"2026-08-18T09:30:00\""), json);
assertTrue(json.contains("\"baseDate\":\"2026-08-18\""), json);
assertTrue(json.contains("\"name\":\"포털\""), json);
});
}
@Test
@DisplayName("ISO-8601 문자열은 다시 날짜 타입으로 역직렬화된다")
void isoStringsDeserializeBack() {
withBootObjectMapper(objectMapper -> {
String json = "{\"createdAt\":\"2026-08-18T09:30:00\",\"baseDate\":\"2026-08-18\",\"name\":\"포털\"}";
SampleDTO dto = objectMapper.readValue(json, SampleDTO.class);
assertEquals(LocalDateTime.of(2026, 8, 18, 9, 30, 0), dto.getCreatedAt());
assertEquals(LocalDate.of(2026, 8, 18), dto.getBaseDate());
});
}
@Test
@DisplayName("모르는 필드가 있어도 역직렬화가 실패하지 않는다 - Boot 기본 동작 유지")
void unknownPropertiesAreIgnored() {
withBootObjectMapper(objectMapper -> {
String json = "{\"name\":\"포털\",\"unknownField\":123}";
SampleDTO dto = objectMapper.readValue(json, SampleDTO.class);
assertEquals("포털", dto.getName());
});
}
}
@@ -0,0 +1,56 @@
package com.eactive.apim.portal.common.security;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
/**
* commons-beanutils 1.11.0(CVE-2025-48734) 상향 회귀 테스트.
* <p>
* 1.11.0 부터 SuppressPropertiesBeanIntrospector 가 기본 활성이라 enum 의 declaredClass 를 통해
* ClassLoader 로 내려가는 경로가 막힌다. 검증기들이 쓰는 일반 프로퍼티 접근은 그대로 동작해야 한다.
* </p>
*/
class BeanUtilsPropertyAccessTest {
enum Kind {
A, B
}
public static class Form {
private String password = "pw1234";
private Kind kind = Kind.A;
public String getPassword() {
return password;
}
public Kind getKind() {
return kind;
}
}
@Test
@DisplayName("일반 프로퍼티 접근은 정상 - PasswordRuleValidator 등이 쓰는 경로")
void plainPropertyAccessWorks() throws Exception {
assertEquals("pw1234", PropertyUtils.getProperty(new Form(), "password"));
assertEquals(Kind.A, PropertyUtils.getProperty(new Form(), "kind"));
}
@Test
@DisplayName("enum 의 declaredClass 접근은 차단된다")
void declaredClassIsSuppressed() {
assertThrows(NoSuchMethodException.class,
() -> PropertyUtils.getProperty(new Form(), "kind.declaredClass"));
}
@Test
@DisplayName("declaredClass 를 거쳐 ClassLoader 로 내려가는 경로도 차단된다")
void classLoaderIsUnreachable() {
assertThrows(NoSuchMethodException.class,
() -> PropertyUtils.getNestedProperty(new Form(), "kind.declaredClass.classLoader"));
}
}
@@ -0,0 +1,51 @@
package com.eactive.apim.portal.common.security;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource;
/**
* snakeyaml 2.6(CVE-2022-1471) 상향 회귀 테스트.
* <p>
* application.yml 로딩 경로가 정상 동작하는지, 그리고 임의 타입을 지정하는 글로벌 태그(!!java...)가
* 거부되는지 확인한다.
* </p>
*/
class YamlLoaderTagTest {
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
private List<PropertySource<?>> load(String yaml) throws IOException {
return loader.load("test", new ByteArrayResource(yaml.getBytes("UTF-8")));
}
@Test
@DisplayName("일반 yml 은 그대로 로딩된다")
void plainYamlLoads() throws Exception {
List<PropertySource<?>> sources = load("server:\n port: 39130\nportal:\n auth-ttl: 300\n");
assertEquals(1, sources.size());
assertEquals(39130, sources.get(0).getProperty("server.port"));
assertEquals(300, sources.get(0).getProperty("portal.auth-ttl"));
}
@Test
@DisplayName("임의 타입을 지정하는 글로벌 태그는 거부된다")
void globalTagIsRejected() {
String payload = "key: !!javax.script.ScriptEngineManager [!!java.net.URL [\"http://127.0.0.1/\"]]\n";
Exception thrown = assertThrows(Exception.class, () -> load(payload));
assertNotNull(thrown.getMessage());
assertEquals(true, thrown.getMessage().contains("Global tag is not allowed"));
}
}
@@ -0,0 +1,100 @@
package com.eactive.apim.portal.config;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
/**
* commons-fileupload 1.6.0(CVE-2025-48976) 상향에 따른 파트 헤더 상한 회귀 테스트.
* <p>
* 1.6 부터 파트 헤더 총량 기본 상한이 512 바이트라 한글 파일명(UTF-8 3바이트/자)이 길면 업로드가 깨진다.
* {@link MultipartConfig} 가 상한을 2048 로 올려 두는지 파싱 결과로 확인한다.
* </p>
*/
class MultipartConfigTest {
private static final String BOUNDARY = "----portalTestBoundary";
private CommonsMultipartResolver resolver() {
PortalProperties properties = new PortalProperties();
properties.getFile().setMaxSize("8MB");
return new MultipartConfig(properties).filterMultipartResolver();
}
private MockHttpServletRequest multipartRequest(String filename) throws IOException {
String head = "--" + BOUNDARY + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"" + filename + "\"\r\n"
+ "Content-Type: application/octet-stream\r\n\r\n";
ByteArrayOutputStream body = new ByteArrayOutputStream();
body.write(head.getBytes("UTF-8"));
body.write("hello".getBytes("UTF-8"));
body.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes("UTF-8"));
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/file/upload");
request.setContentType("multipart/form-data; boundary=" + BOUNDARY);
request.setCharacterEncoding("UTF-8");
request.setContent(body.toByteArray());
return request;
}
private String korean(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append('한');
}
return sb.append(".pdf").toString();
}
@Test
@DisplayName("영문 파일명 업로드는 그대로 파싱된다")
void asciiFilename() throws Exception {
MultipartHttpServletRequest parsed = resolver().resolveMultipart(multipartRequest("report.pdf"));
MultipartFile file = parsed.getFile("file");
assertNotNull(file);
assertEquals("report.pdf", file.getOriginalFilename());
assertEquals(5L, file.getSize());
}
@Test
@DisplayName("한글 파일명 137자 이상도 파싱된다 - fileupload 1.6 기본 상한 512 였다면 실패할 길이")
void longKoreanFilenameOverDefaultLimit() throws Exception {
String filename = korean(150);
MultipartHttpServletRequest parsed = resolver().resolveMultipart(multipartRequest(filename));
MultipartFile file = parsed.getFile("file");
assertNotNull(file);
assertEquals(filename, file.getOriginalFilename());
}
@Test
@DisplayName("OS 파일명 상한(255자)이 전부 한글이어도 파싱된다")
void koreanFilenameAtOsLimit() throws Exception {
String filename = korean(255);
MultipartHttpServletRequest parsed = resolver().resolveMultipart(multipartRequest(filename));
MultipartFile file = parsed.getFile("file");
assertNotNull(file);
assertEquals(filename, file.getOriginalFilename());
}
@Test
@DisplayName("파트 헤더가 상한(2048바이트)을 넘으면 거부한다 - DoS 방어가 살아 있음")
void partHeaderOverConfiguredLimitIsRejected() {
assertThrows(MultipartException.class,
() -> resolver().resolveMultipart(multipartRequest(korean(700))));
}
}
@@ -0,0 +1,70 @@
package com.eactive.apim.portal.config;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
/**
* CVE-2026-22732 우회책 회귀 테스트.
* <p>
* {@code PortalConfigSecurity} 는 HeaderWriterFilter 를 eager 모드로 후처리한다. eager 모드에서는
* 컨트롤러가 실행되기 전에 보안 헤더가 이미 기록돼 있어야 하며, 응답이 먼저 커밋되더라도 헤더가 누락되지 않는다.
* </p>
*/
class SecurityHeaderEagerWriteTest {
private HeaderWriterFilter filter(boolean eager) {
HeaderWriterFilter filter = new HeaderWriterFilter(Arrays.asList(
new XContentTypeOptionsHeaderWriter(),
new CacheControlHeadersWriter()));
filter.setShouldWriteHeadersEagerly(eager);
return filter;
}
@Test
@DisplayName("eager 모드에서는 체인(컨트롤러) 실행 시점에 이미 보안 헤더가 기록돼 있다")
void eagerWritesHeadersBeforeChain() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/file/download");
MockHttpServletResponse response = new MockHttpServletResponse();
AtomicReference<String> seenInChain = new AtomicReference<>();
filter(true).doFilter(request, response, (req, res) -> {
seenInChain.set(((HttpServletResponse) res).getHeader("X-Content-Type-Options"));
new MockFilterChain().doFilter(req, res);
});
assertEquals("nosniff", seenInChain.get(), "체인 진입 시점에 헤더가 있어야 한다");
assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
assertNotNull(response.getHeader("Cache-Control"));
}
@Test
@DisplayName("기본(lazy) 모드에서는 체인 실행 시점에 헤더가 아직 없다 - eager 설정이 실제로 의미 있음을 고정")
void lazyDoesNotWriteHeadersBeforeChain() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/file/download");
MockHttpServletResponse response = new MockHttpServletResponse();
AtomicReference<String> seenInChain = new AtomicReference<>();
filter(false).doFilter(request, response, (req, res) -> {
seenInChain.set(((HttpServletResponse) res).getHeader("X-Content-Type-Options"));
new MockFilterChain().doFilter(req, res);
});
assertNull(seenInChain.get(), "lazy 모드는 체인 이후에 헤더를 쓴다");
assertEquals("nosniff", response.getHeader("X-Content-Type-Options"));
}
}