@kanaries/graphic-walker 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { Specification } from 'visual-insights';
2
2
  import { IField, IRow } from '../interfaces';
3
3
  import { IPredicate } from '../utils';
4
- export declare type IReasonType = 'selection_dim_distribution' | 'selection_mea_distribution' | 'children_major_factor' | 'children_outlier';
4
+ export type IReasonType = 'selection_dim_distribution' | 'selection_mea_distribution' | 'children_major_factor' | 'children_outlier';
5
5
  export declare const geomTypeMap: {
6
6
  [key: string]: any;
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { StatFuncName } from "visual-insights/build/esm/statistics";
2
2
  import { AggFC } from 'cube-core/built/types';
3
3
  import { IAnalyticType, ISemanticType } from 'visual-insights';
4
- export declare type DeepReadonly<T extends Record<keyof any, any>> = {
4
+ export type DeepReadonly<T extends Record<keyof any, any>> = {
5
5
  readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
6
6
  };
7
7
  export interface IRow {
@@ -10,7 +10,7 @@ export interface IRow {
10
10
  /**
11
11
  * @deprecated
12
12
  */
13
- export declare type SemanticType = 'quantitative' | 'nominal' | 'ordinal' | 'temporal';
13
+ export type SemanticType = 'quantitative' | 'nominal' | 'ordinal' | 'temporal';
14
14
  export interface Filters {
15
15
  [key: string]: any[];
16
16
  }
@@ -102,7 +102,7 @@ export interface IDraggableStateKey {
102
102
  id: keyof DraggableFieldState;
103
103
  mode: number;
104
104
  }
105
- export declare type IFilterRule = {
105
+ export type IFilterRule = {
106
106
  type: 'range';
107
107
  value: readonly [number, number];
108
108
  } | {
@@ -114,9 +114,9 @@ export declare type IFilterRule = {
114
114
  };
115
115
  export declare const EXPLORATION_TYPES: readonly ["none", "brush", "point"];
116
116
  export declare const BRUSH_DIRECTIONS: readonly ["default", "x", "y"];
117
- export declare type IStackMode = 'none' | 'stack' | 'normalize';
118
- export declare type IExplorationType = (typeof EXPLORATION_TYPES)[number];
119
- export declare type IBrushDirection = (typeof BRUSH_DIRECTIONS)[number];
117
+ export type IStackMode = 'none' | 'stack' | 'normalize';
118
+ export type IExplorationType = (typeof EXPLORATION_TYPES)[number];
119
+ export type IBrushDirection = (typeof BRUSH_DIRECTIONS)[number];
120
120
  export interface IVisualConfig {
121
121
  defaultAggregated: boolean;
122
122
  geoms: string[];
@@ -1,3 +1,4 @@
1
1
  import React from 'react';
2
- declare const _default: React.FunctionComponent<{}>;
2
+ import { IReactVegaHandler } from '../vis/react-vega';
3
+ declare const _default: React.MemoExoticComponent<React.ForwardRefExoticComponent<Pick<React.RefAttributes<IReactVegaHandler>, "key"> & React.RefAttributes<IReactVegaHandler>>>;
3
4
  export default _default;
@@ -2,7 +2,7 @@ import { Specification } from "visual-insights";
2
2
  import { DataSet, DraggableFieldState, IFilterRule, IViewField, IVisualConfig } from "../interfaces";
3
3
  import { VisSpecWithHistory } from "../models/visSpecHistory";
4
4
  import { CommonStore } from "./commonStore";
5
- declare type DeepReadonly<T extends Record<keyof any, any>> = {
5
+ type DeepReadonly<T extends Record<keyof any, any>> = {
6
6
  readonly [K in keyof T]: T[K] extends Record<keyof any, any> ? DeepReadonly<T[K]> : T[K];
7
7
  };
8
8
  export declare class VizSpecStore {
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com *//*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */*,:before,:after{box-sizing:border-box}html{tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type=button],[type=submit]{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}pre,code,kbd,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-2{top:.5rem}.right-2{right:.5rem}.z-auto{z-index:auto}.col-span-1{grid-column:span 1 / span 1}.col-span-2{grid-column:span 2 / span 2}.col-span-3{grid-column:span 3 / span 3}.col-span-5{grid-column:span 5 / span 5}.col-span-7{grid-column:span 7 / span 7}.float-right{float:right}.m-1{margin:.25rem}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mr-0{margin-right:0}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-0\.5{margin-right:.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.-mb-px{margin-bottom:-1px}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-4{height:1rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-16{height:4rem}.h-48{height:12rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-16{width:4rem}.w-60{width:15rem}.w-full{width:100%}.min-w-96{min-width:96px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.transform{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;user-select:none}.resize{resize:both}.appearance-none{-webkit-appearance:none;appearance:none}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-center{justify-content:center}.self-center{align-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border-2{border-width:2px}.border{border-width:1px}.border-t-2{border-top-width:2px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-l{border-left-width:1px}.border-transparent{border-color:transparent}.border-gray-200{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity: 1;border-color:rgba(156,163,175,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-blue-400{--tw-border-opacity: 1;border-color:rgba(96,165,250,var(--tw-border-opacity))}.border-blue-500{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgba(75,85,99,var(--tw-border-opacity))}.focus\:border-gray-500:focus{--tw-border-opacity: 1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgba(156,163,175,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgba(217,119,6,var(--tw-bg-opacity))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgba(219,234,254,var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.bg-indigo-50{--tw-bg-opacity: 1;background-color:rgba(238,242,255,var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.hover\:bg-red-100:hover{--tw-bg-opacity: 1;background-color:rgba(254,226,226,var(--tw-bg-opacity))}.hover\:bg-yellow-100:hover{--tw-bg-opacity: 1;background-color:rgba(254,243,199,var(--tw-bg-opacity))}.hover\:bg-yellow-500:hover{--tw-bg-opacity: 1;background-color:rgba(245,158,11,var(--tw-bg-opacity))}.hover\:bg-green-50:hover{--tw-bg-opacity: 1;background-color:rgba(236,253,245,var(--tw-bg-opacity))}.hover\:bg-blue-100:hover{--tw-bg-opacity: 1;background-color:rgba(219,234,254,var(--tw-bg-opacity))}.disabled\:bg-gray-300:disabled{--tw-bg-opacity: 1;background-color:rgba(209,213,219,var(--tw-bg-opacity))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-0\.5{padding-top:.125rem}.pr-2{padding-right:.5rem}.pr-6{padding-right:1.5rem}.pb-0{padding-bottom:0}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-0\.5{padding-bottom:.125rem}.pl-2{padding-left:.5rem}.pl-6{padding-left:1.5rem}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.ordinal{--tw-ordinal: var(--tw-empty, );--tw-slashed-zero: var(--tw-empty, );--tw-numeric-figure: var(--tw-empty, );--tw-numeric-spacing: var(--tw-empty, );--tw-numeric-fraction: var(--tw-empty, );font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.ordinal{--tw-ordinal: ordinal}.leading-none{line-height:1}.text-black{--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgba(245,158,11,var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgba(16,185,129,var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.hover\:text-purple-600:hover{--tw-text-opacity: 1;color:rgba(124,58,237,var(--tw-text-opacity))}.underline{text-decoration:underline}*,:before,:after{--tw-shadow: 0 0 #0000}.shadow{--tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}*,:before,:after{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000}.filter{--tw-blur: var(--tw-empty, );--tw-brightness: var(--tw-empty, );--tw-contrast: var(--tw-empty, );--tw-grayscale: var(--tw-empty, );--tw-hue-rotate: var(--tw-empty, );--tw-invert: var(--tw-empty, );--tw-saturate: var(--tw-empty, );--tw-sepia: var(--tw-empty, );--tw-drop-shadow: var(--tw-empty, );filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.blur{--tw-blur: blur(8px)}@media (min-width: 1280px){.xl\:col-span-1{grid-column:span 1 / span 1}.xl\:col-span-4{grid-column:span 4 / span 4}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}
1
+ /*! tailwindcss v2.2.19 | MIT License | https://tailwindcss.com *//*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */*,:before,:after{box-sizing:border-box}html{tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"}hr{height:0;color:inherit}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type=button],[type=submit]{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}button{background-color:transparent;background-image:none}fieldset{margin:0;padding:0}ol,ul{list-style:none;margin:0;padding:0}html{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";line-height:1.5}body{font-family:inherit;line-height:inherit}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:currentColor}hr{border-top-width:1px}img{border-style:solid}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}button,input,optgroup,select,textarea{padding:0;line-height:inherit;color:inherit}pre,code,kbd,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-2{top:.5rem}.right-2{right:.5rem}.z-auto{z-index:auto}.col-span-1{grid-column:span 1 / span 1}.col-span-5{grid-column:span 5 / span 5}.float-right{float:right}.m-1{margin:.25rem}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mr-0{margin-right:0}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-0\.5{margin-right:.125rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.-mb-px{margin-bottom:-1px}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-16{height:4rem}.h-48{height:12rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-16{width:4rem}.w-60{width:15rem}.w-full{width:100%}.min-w-96{min-width:96px}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.flex-shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.transform{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{transform:scale(2);opacity:0}}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{transform:translateY(-25%);animation-timing-function:cubic-bezier(.8,0,1,1)}50%{transform:none;animation-timing-function:cubic-bezier(0,0,.2,1)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;user-select:none}.resize{resize:both}.appearance-none{-webkit-appearance:none;appearance:none}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-center{justify-content:center}.self-center{align-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded-sm{border-radius:.125rem}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.border-2{border-width:2px}.border{border-width:1px}.border-t-2{border-top-width:2px}.border-t{border-top-width:1px}.border-r{border-right-width:1px}.border-b{border-bottom-width:1px}.border-l-2{border-left-width:2px}.border-l{border-left-width:1px}.border-transparent{border-color:transparent}.border-gray-200{--tw-border-opacity: 1;border-color:rgba(229,231,235,var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity: 1;border-color:rgba(156,163,175,var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.border-blue-400{--tw-border-opacity: 1;border-color:rgba(96,165,250,var(--tw-border-opacity))}.border-blue-500{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgba(209,213,219,var(--tw-border-opacity))}.hover\:border-gray-600:hover{--tw-border-opacity: 1;border-color:rgba(75,85,99,var(--tw-border-opacity))}.focus\:border-gray-500:focus{--tw-border-opacity: 1;border-color:rgba(107,114,128,var(--tw-border-opacity))}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgba(59,130,246,var(--tw-border-opacity))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgba(156,163,175,var(--tw-bg-opacity))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgba(239,68,68,var(--tw-bg-opacity))}.bg-yellow-600{--tw-bg-opacity: 1;background-color:rgba(217,119,6,var(--tw-bg-opacity))}.bg-blue-100{--tw-bg-opacity: 1;background-color:rgba(219,234,254,var(--tw-bg-opacity))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgba(37,99,235,var(--tw-bg-opacity))}.bg-indigo-50{--tw-bg-opacity: 1;background-color:rgba(238,242,255,var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgba(249,250,251,var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgba(243,244,246,var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity: 1;background-color:rgba(229,231,235,var(--tw-bg-opacity))}.hover\:bg-red-100:hover{--tw-bg-opacity: 1;background-color:rgba(254,226,226,var(--tw-bg-opacity))}.hover\:bg-yellow-100:hover{--tw-bg-opacity: 1;background-color:rgba(254,243,199,var(--tw-bg-opacity))}.hover\:bg-yellow-500:hover{--tw-bg-opacity: 1;background-color:rgba(245,158,11,var(--tw-bg-opacity))}.hover\:bg-green-50:hover{--tw-bg-opacity: 1;background-color:rgba(236,253,245,var(--tw-bg-opacity))}.hover\:bg-blue-100:hover{--tw-bg-opacity: 1;background-color:rgba(219,234,254,var(--tw-bg-opacity))}.disabled\:bg-gray-300:disabled{--tw-bg-opacity: 1;background-color:rgba(209,213,219,var(--tw-bg-opacity))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-0\.5{padding-top:.125rem}.pr-2{padding-right:.5rem}.pr-6{padding-right:1.5rem}.pb-0{padding-bottom:0}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-0\.5{padding-bottom:.125rem}.pl-2{padding-left:.5rem}.pl-6{padding-left:1.5rem}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.ordinal{--tw-ordinal: var(--tw-empty, );--tw-slashed-zero: var(--tw-empty, );--tw-numeric-figure: var(--tw-empty, );--tw-numeric-spacing: var(--tw-empty, );--tw-numeric-fraction: var(--tw-empty, );font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.ordinal{--tw-ordinal: ordinal}.leading-none{line-height:1}.text-black{--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgba(209,213,219,var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgba(156,163,175,var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgba(107,114,128,var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgba(75,85,99,var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgba(31,41,55,var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgba(17,24,39,var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgba(220,38,38,var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgba(245,158,11,var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgba(16,185,129,var(--tw-text-opacity))}.text-blue-500{--tw-text-opacity: 1;color:rgba(59,130,246,var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgba(55,65,81,var(--tw-text-opacity))}.hover\:text-purple-600:hover{--tw-text-opacity: 1;color:rgba(124,58,237,var(--tw-text-opacity))}.underline{text-decoration:underline}*,:before,:after{--tw-shadow: 0 0 #0000}.shadow{--tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, .1), 0 1px 2px 0 rgba(0, 0, 0, .06);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -2px rgba(0, 0, 0, .05);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}*,:before,:after{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000}.filter{--tw-blur: var(--tw-empty, );--tw-brightness: var(--tw-empty, );--tw-contrast: var(--tw-empty, );--tw-grayscale: var(--tw-empty, );--tw-hue-rotate: var(--tw-empty, );--tw-invert: var(--tw-empty, );--tw-saturate: var(--tw-empty, );--tw-sepia: var(--tw-empty, );--tw-drop-shadow: var(--tw-empty, );filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.blur{--tw-blur: blur(8px)}@media (min-width: 768px){.md\:col-span-2{grid-column:span 2 / span 2}.md\:col-span-3{grid-column:span 3 / span 3}.md\:col-span-7{grid-column:span 7 / span 7}.md\:grid{display:grid}.md\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.md\:flex-col{flex-direction:column}}@media (min-width: 1280px){.xl\:col-span-1{grid-column:span 1 / span 1}.xl\:col-span-4{grid-column:span 4 / span 4}.xl\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}
@@ -1,5 +1,11 @@
1
1
  import React from 'react';
2
2
  import { IViewField, IRow, IStackMode } from '../interfaces';
3
+ export interface IReactVegaHandler {
4
+ getSVGData: () => Promise<string[]>;
5
+ getCanvasData: () => Promise<string[]>;
6
+ downloadSVG: (filename?: string) => Promise<string[]>;
7
+ downloadPNG: (filename?: string) => Promise<string[]>;
8
+ }
3
9
  interface ReactVegaProps {
4
10
  rows: Readonly<IViewField[]>;
5
11
  columns: Readonly<IViewField[]>;
@@ -43,5 +49,5 @@ interface SingleViewProps {
43
49
  selectEncoding: 'default' | 'none';
44
50
  brushEncoding: 'x' | 'y' | 'default' | 'none';
45
51
  }
46
- declare const ReactVega: React.FC<ReactVegaProps>;
52
+ declare const ReactVega: React.ForwardRefExoticComponent<ReactVegaProps & React.RefAttributes<IReactVegaHandler>>;
47
53
  export default ReactVega;
@@ -1,4 +1,8 @@
1
1
  import React from 'react';
2
+ import { IReactVegaHandler } from '../vis/react-vega';
2
3
  export declare const LiteContainer: import("styled-components").StyledComponent<"div", any, {}, never>;
3
- declare const _default: React.FunctionComponent<{}>;
4
+ interface IVisualSettings {
5
+ rendererHandler?: React.RefObject<IReactVegaHandler>;
6
+ }
7
+ declare const _default: React.FunctionComponent<IVisualSettings>;
4
8
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanaries/graphic-walker",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "scripts": {
5
5
  "dev:front_end": "vite --host",
6
6
  "dev": "npm run dev:front_end",
@@ -61,6 +61,7 @@
61
61
  "@types/styled-components": "^5.1.26",
62
62
  "@types/uuid": "^8.3.1",
63
63
  "@vitejs/plugin-react-refresh": "^1.3.6",
64
+ "styled-components": "^5.3.6",
64
65
  "typescript": "^4.3.2",
65
66
  "vite": "^3.1.0"
66
67
  },
package/src/App.tsx CHANGED
@@ -1,10 +1,11 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useRef } from 'react';
2
2
  import { Specification } from 'visual-insights';
3
3
  import { observer } from 'mobx-react-lite';
4
4
  import { LightBulbIcon } from '@heroicons/react/24/outline'
5
5
  import { toJS } from 'mobx';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { IMutField, IRow } from './interfaces';
8
+ import type { IReactVegaHandler } from './vis/react-vega';
8
9
  import VisualSettings from './visualSettings';
9
10
  import { Container, NestContainer } from './components/container';
10
11
  import ClickMenu from './components/clickMenu';
@@ -88,6 +89,8 @@ const App: React.FC<EditorProps> = props => {
88
89
  }
89
90
  }, [currentDataset, spec]);
90
91
 
92
+ const rendererRef = useRef<IReactVegaHandler>(null);
93
+
91
94
  return (
92
95
  <div className="App">
93
96
  { !hideDataSourceConfig && <DataSourceSegment preWorkDone={insightReady} />}
@@ -97,23 +100,23 @@ const App: React.FC<EditorProps> = props => {
97
100
  </div>
98
101
  <Container style={{ marginTop: '0em', borderTop: 'none' }}>
99
102
  <Menubar />
100
- <VisualSettings />
101
- <div className="grid grid-cols-12 xl:grid-cols-6">
102
- <div className="col-span-3 xl:col-span-1">
103
+ <VisualSettings rendererHandler={rendererRef} />
104
+ <div className="md:grid md:grid-cols-12 xl:grid-cols-6">
105
+ <div className="md:col-span-3 xl:col-span-1">
103
106
  <DatasetFields />
104
107
  </div>
105
- <div className="col-span-2 xl:col-span-1">
108
+ <div className="md:col-span-2 xl:col-span-1">
106
109
  <FilterField />
107
110
  <AestheticFields />
108
111
  </div>
109
- <div className="col-span-7 xl:col-span-4">
112
+ <div className="md:col-span-7 xl:col-span-4">
110
113
  <div>
111
114
  <PosFields />
112
115
  </div>
113
116
  <NestContainer style={{ minHeight: '600px', overflow: 'auto' }} onMouseLeave={() => {
114
117
  vizEmbededMenu.show && commonStore.closeEmbededMenu();
115
118
  }}>
116
- {datasets.length > 0 && <ReactiveRenderer />}
119
+ {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} />}
117
120
  <InsightBoard />
118
121
  {vizEmbededMenu.show && (
119
122
  <ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
@@ -2,7 +2,6 @@ import React, { useRef } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { XCircleIcon } from '@heroicons/react/24/outline';
4
4
 
5
-
6
5
  const Background = styled.div({
7
6
  position: 'fixed',
8
7
  left: 0,
@@ -14,7 +13,13 @@ const Background = styled.div({
14
13
  });
15
14
 
16
15
  const Container = styled.div`
17
- width: 880px;
16
+ width: 98%;
17
+ @media (min-width: 600px) {
18
+ width: 80%;
19
+ }
20
+ @media (min-width: 1100px) {
21
+ width: 880px;
22
+ }
18
23
  max-height: 800px;
19
24
  overflow: auto;
20
25
  > div.header {
@@ -1,17 +1,24 @@
1
1
  import React from "react";
2
2
  import { Droppable } from "react-beautiful-dnd";
3
3
  import { useTranslation } from "react-i18next";
4
+ import styled from 'styled-components';
4
5
  import { NestContainer } from "../../components/container";
5
6
  import DimFields from "./dimFields";
6
7
  import MeaFields from "./meaFields";
7
8
 
9
+ const DSContainer = styled(NestContainer)`
10
+ @media (min-width: 768px) {
11
+ height: 680px;
12
+ }
13
+ `
14
+
8
15
  const DatasetFields: React.FC = (props) => {
9
16
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.DatasetFields" });
10
17
 
11
18
  return (
12
- <NestContainer className="flex flex-col" style={{ height: "680px", paddingBlock: 0 }}>
19
+ <DSContainer className="flex md:flex-col" style={{ paddingBlock: 0 }}>
13
20
  <h4 className="text-xs mb-2 flex-grow-0 cursor-default select-none mt-2">{t("field_list")}</h4>
14
- <div className="pd-1 overflow-y-auto" style={{ maxHeight: "380px" }}>
21
+ <div className="pd-1 overflow-y-auto" style={{ maxHeight: "380px", minHeight: '100px' }}>
15
22
  <Droppable droppableId="dimensions" direction="vertical">
16
23
  {(provided, snapshot) => <DimFields provided={provided} />}
17
24
  </Droppable>
@@ -21,7 +28,7 @@ const DatasetFields: React.FC = (props) => {
21
28
  {(provided, snapshot) => <MeaFields provided={provided} />}
22
29
  </Droppable>
23
30
  </div>
24
- </NestContainer>
31
+ </DSContainer>
25
32
  );
26
33
  };
27
34
 
@@ -55,5 +55,10 @@ export const DRAGGABLE_STATE_KEYS: Readonly<IDraggableStateKey[]> = [
55
55
  export const AGGREGATOR_LIST: Readonly<string[]> = [
56
56
  'sum',
57
57
  'mean',
58
+ 'median',
58
59
  'count',
60
+ 'min',
61
+ 'max',
62
+ 'variance',
63
+ 'stdev'
59
64
  ] as const;
@@ -57,7 +57,14 @@
57
57
  "aggregator": {
58
58
  "sum": "Sum",
59
59
  "mean": "Mean",
60
- "count": "Count"
60
+ "count": "Count",
61
+ "median": "Median",
62
+ "min": "Min",
63
+ "max": "Max",
64
+ "q1": "Q1",
65
+ "q3": "Q3",
66
+ "stdev": "Standard Deviation",
67
+ "variance": "Variance"
61
68
  },
62
69
  "filter_type": {
63
70
  "one_of": "One-Of",
@@ -116,7 +123,9 @@
116
123
  "button": {
117
124
  "ascending": "Sort in Ascending Order",
118
125
  "descending": "Sort in Descending Order",
119
- "transpose": "Transpose"
126
+ "transpose": "Transpose",
127
+ "export_chart": "Export",
128
+ "export_chart_as": "Export as {{type}}"
120
129
  },
121
130
  "size": "Resize",
122
131
  "size_setting": {
@@ -57,7 +57,14 @@
57
57
  "aggregator": {
58
58
  "sum": "求和",
59
59
  "mean": "平均值",
60
- "count": "计数"
60
+ "count": "计数",
61
+ "median": "中位数",
62
+ "min": "最小值",
63
+ "max": "最大值",
64
+ "q1": "Q1",
65
+ "q3": "Q3",
66
+ "stdev": "标准差",
67
+ "variance": "方差"
61
68
  },
62
69
  "filter_type": {
63
70
  "one_of": "按值筛选",
@@ -116,7 +123,9 @@
116
123
  "button": {
117
124
  "ascending": "升序排序",
118
125
  "descending": "降序排序",
119
- "transpose": "转置"
126
+ "transpose": "转置",
127
+ "export_chart": "导出",
128
+ "export_chart_as": "导出 {{type}}"
120
129
  },
121
130
  "size": "调整尺寸",
122
131
  "size_setting": {
@@ -1,13 +1,13 @@
1
1
  import { runInAction, toJS } from 'mobx';
2
2
  import { observer } from 'mobx-react-lite';
3
3
  import { Resizable } from 're-resizable';
4
- import React, { useState, useCallback, useEffect, useRef } from 'react';
4
+ import React, { useState, useCallback, useEffect, useRef, forwardRef } from 'react';
5
5
  import { applyFilter } from '../services';
6
6
  import { useGlobalStore } from '../store';
7
- import ReactVega from '../vis/react-vega';
7
+ import ReactVega, { IReactVegaHandler } from '../vis/react-vega';
8
8
 
9
9
 
10
- const ReactiveRenderer: React.FC = props => {
10
+ const ReactiveRenderer = forwardRef<IReactVegaHandler, {}>(function ReactiveRenderer (props, ref) {
11
11
  const { vizStore, commonStore } = useGlobalStore();
12
12
  const { draggableFieldState, visualConfig } = vizStore;
13
13
  const { geoms, interactiveScale, defaultAggregated, stack, showActions, size, exploration } = visualConfig;
@@ -100,11 +100,12 @@ const ReactiveRenderer: React.FC = props => {
100
100
  showActions={showActions}
101
101
  width={size.width - 12 * 4}
102
102
  height={size.height - 12 * 4}
103
+ ref={ref}
103
104
  brushEncoding={exploration.mode === 'brush' ? exploration.brushDirection : 'none'}
104
105
  selectEncoding={exploration.mode === 'point' ? 'default' : 'none'}
105
106
  onGeomClick={handleGeomClick}
106
107
  />
107
108
  </Resizable>
108
- }
109
+ });
109
110
 
110
111
  export default observer(ReactiveRenderer);
@@ -1,8 +1,8 @@
1
- import React, { useEffect, useState, useMemo, useRef } from 'react';
1
+ import React, { useEffect, useState, useMemo, forwardRef, useImperativeHandle, useRef } from 'react';
2
2
  import embed from 'vega-embed';
3
3
  import { Subject, Subscription } from 'rxjs'
4
4
  import * as op from 'rxjs/operators';
5
- import { ScenegraphEvent } from 'vega';
5
+ import type { ScenegraphEvent, View } from 'vega';
6
6
  import { ISemanticType } from 'visual-insights';
7
7
  import styled from 'styled-components';
8
8
  import { autoMark } from '../utils/autoMark';
@@ -17,6 +17,12 @@ const CanvaContainer = styled.div<{rowSize: number; colSize: number;}>`
17
17
  `
18
18
 
19
19
  const SELECTION_NAME = 'geom';
20
+ export interface IReactVegaHandler {
21
+ getSVGData: () => Promise<string[]>;
22
+ getCanvasData: () => Promise<string[]>;
23
+ downloadSVG: (filename?: string) => Promise<string[]>;
24
+ downloadPNG: (filename?: string) => Promise<string[]>;
25
+ }
20
26
  interface ReactVegaProps {
21
27
  rows: Readonly<IViewField[]>;
22
28
  columns: Readonly<IViewField[]>;
@@ -351,7 +357,7 @@ function getSingleView(props: SingleViewProps) {
351
357
  } : encoding,
352
358
  };
353
359
  }
354
- const ReactVega: React.FC<ReactVegaProps> = props => {
360
+ const ReactVega = forwardRef<IReactVegaHandler, ReactVegaProps>(function ReactVega (props, ref) {
355
361
  const {
356
362
  dataSource = [],
357
363
  rows = [],
@@ -408,7 +414,10 @@ const ReactVega: React.FC<ReactVegaProps> = props => {
408
414
  })
409
415
  }, [rowRepeatFields, colRepeatFields])
410
416
 
417
+ const vegaRefs = useRef<View[]>([]);
418
+
411
419
  useEffect(() => {
420
+ vegaRefs.current = [];
412
421
 
413
422
  const yField = rows.length > 0 ? rows[rows.length - 1] : NULL_FIELD;
414
423
  const xField = columns.length > 0 ? columns[columns.length - 1] : NULL_FIELD;
@@ -495,6 +504,7 @@ const ReactVega: React.FC<ReactVegaProps> = props => {
495
504
  // console.log(JSON.stringify(spec, undefined, 2));
496
505
  if (viewPlaceholders.length > 0 && viewPlaceholders[0].current) {
497
506
  embed(viewPlaceholders[0].current, spec, { mode: 'vega-lite', actions: showActions }).then(res => {
507
+ vegaRefs.current = [res.view];
498
508
  try {
499
509
  res.view.addEventListener('click', (e) => {
500
510
  click$.next(e);
@@ -576,6 +586,7 @@ const ReactVega: React.FC<ReactVegaProps> = props => {
576
586
  // console.log(JSON.stringify(ans, undefined, 2));
577
587
  if (node) {
578
588
  embed(node, ans, { mode: 'vega-lite', actions: showActions }).then(res => {
589
+ vegaRefs.current.push(res.view);
579
590
  // 这种 case 下,我们来考虑联动的 params
580
591
  // vega 使用 Data 来维护 params 的状态,只需要打通这些状态就可以实现联动
581
592
  const paramStores = (res.vgSpec.data?.map(d => d.name) ?? []).filter(
@@ -670,12 +681,52 @@ const ReactVega: React.FC<ReactVegaProps> = props => {
670
681
  crossFilterTriggerIdx,
671
682
  ]);
672
683
 
684
+ useImperativeHandle(ref, () => ({
685
+ getSVGData() {
686
+ return Promise.all(vegaRefs.current.map(view => view.toSVG()));
687
+ },
688
+ async getCanvasData() {
689
+ const canvases = await Promise.all(vegaRefs.current.map(view => view.toCanvas()));
690
+ return canvases.map(canvas => canvas.toDataURL('image/png'));
691
+ },
692
+ async downloadSVG(filename = `gw chart ${Date.now() % 1_000_000}`.padStart(6, '0')) {
693
+ const data = await Promise.all(vegaRefs.current.map(view => view.toSVG()));
694
+ const files: string[] = [];
695
+ for (let i = 0; i < data.length; i += 1) {
696
+ const d = data[i];
697
+ const file = new File([d], `${filename}${data.length > 1 ? `_${i + 1}` : ''}.svg`);
698
+ const url = URL.createObjectURL(file);
699
+ const a = document.createElement('a');
700
+ a.download = file.name;
701
+ a.href = url;
702
+ a.click();
703
+ requestAnimationFrame(() => {
704
+ URL.revokeObjectURL(url);
705
+ });
706
+ }
707
+ return files;
708
+ },
709
+ async downloadPNG(filename = `gw chart ${Date.now() % 1_000_000}`.padStart(6, '0')) {
710
+ const canvases = await Promise.all(vegaRefs.current.map(view => view.toCanvas(2)));
711
+ const data = canvases.map(canvas => canvas.toDataURL('image/png', 1));
712
+ const files: string[] = [];
713
+ for (let i = 0; i < data.length; i += 1) {
714
+ const d = data[i];
715
+ const a = document.createElement('a');
716
+ a.download = `${filename}${data.length > 1 ? `_${i + 1}` : ''}.png`;
717
+ a.href = d.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
718
+ a.click();
719
+ }
720
+ return files;
721
+ },
722
+ }));
723
+
673
724
  return <CanvaContainer rowSize={Math.max(rowRepeatFields.length, 1)} colSize={Math.max(colRepeatFields.length, 1)}>
674
725
  {/* <div ref={container}></div> */}
675
726
  {
676
727
  viewPlaceholders.map((view, i) => <div key={i} ref={view}></div>)
677
728
  }
678
729
  </CanvaContainer>
679
- }
730
+ });
680
731
 
681
732
  export default ReactVega;
@@ -1,4 +1,4 @@
1
- import { BarsArrowDownIcon, BarsArrowUpIcon } from '@heroicons/react/24/outline';
1
+ import { BarsArrowDownIcon, BarsArrowUpIcon, ChevronDownIcon, PhotoIcon } from '@heroicons/react/24/outline';
2
2
  import { observer } from 'mobx-react-lite';
3
3
  import React from 'react';
4
4
  import styled from 'styled-components'
@@ -9,6 +9,7 @@ import SizeSetting from '../components/sizeSetting';
9
9
  import { GEMO_TYPES, STACK_MODE, CHART_LAYOUT_TYPE } from '../config';
10
10
  import { useGlobalStore } from '../store';
11
11
  import { IStackMode, EXPLORATION_TYPES, IBrushDirection, BRUSH_DIRECTIONS } from '../interfaces';
12
+ import { IReactVegaHandler } from '../vis/react-vega';
12
13
 
13
14
 
14
15
  export const LiteContainer = styled.div`
@@ -16,9 +17,27 @@ export const LiteContainer = styled.div`
16
17
  border: 1px solid #d9d9d9;
17
18
  padding: 1em;
18
19
  background-color: #fff;
20
+ .menu-root {
21
+ position: relative;
22
+ & > *:not(.trigger) {
23
+ display: flex;
24
+ flex-direction: column;
25
+ position: absolute;
26
+ right: 0;
27
+ top: 100%;
28
+ border: 1px solid #8884;
29
+ }
30
+ &:not(:hover) > *:not(.trigger):not(:hover) {
31
+ display: none;
32
+ }
33
+ }
19
34
  `;
20
35
 
21
- const VisualSettings: React.FC = () => {
36
+ interface IVisualSettings {
37
+ rendererHandler?: React.RefObject<IReactVegaHandler>;
38
+ }
39
+
40
+ const VisualSettings: React.FC<IVisualSettings> = ({ rendererHandler }) => {
22
41
  const { vizStore } = useGlobalStore();
23
42
  const { visualConfig, sortCondition } = vizStore;
24
43
  const { t: tGlobal } = useTranslation();
@@ -307,6 +326,48 @@ const VisualSettings: React.FC = () => {
307
326
  {t('toggle.debug')}
308
327
  </label>
309
328
  </div>
329
+ <div className='item'>
330
+ <label
331
+ className="text-xs text-color-gray-700 mr-2"
332
+ htmlFor="button:transpose"
333
+ id="button:transpose:label"
334
+ >
335
+ {t('button.export_chart')}
336
+ </label>
337
+ <PhotoIcon
338
+ className="w-4 inline-block cursor-pointer"
339
+ role="button"
340
+ tabIndex={0}
341
+ id="button:export_chart"
342
+ aria-describedby="button:export_chart:label"
343
+ xlinkTitle={t('button.export_chart')}
344
+ aria-label={t('button.export_chart')}
345
+ onClick={() => rendererHandler?.current?.downloadPNG()}
346
+ />
347
+ <div className="menu-root flex flex-col items-center justify-center">
348
+ <ChevronDownIcon
349
+ className="w-4 h-3 inline-block mr-1 cursor-pointer trigger"
350
+ role="button"
351
+ tabIndex={0}
352
+ />
353
+ <div>
354
+ <button
355
+ className="text-xs min-w-96 w-full pt-1 pb-1 pl-6 pr-6 bg-white hover:bg-gray-200"
356
+ aria-label={t('button.export_chart_as', { type: 'png' })}
357
+ onClick={() => rendererHandler?.current?.downloadPNG()}
358
+ >
359
+ {t('button.export_chart_as', { type: 'png' })}
360
+ </button>
361
+ <button
362
+ className="text-xs min-w-96 w-full pt-1 pb-1 pl-6 pr-6 bg-white hover:bg-gray-200"
363
+ aria-label={t('button.export_chart_as', { type: 'svg' })}
364
+ onClick={() => rendererHandler?.current?.downloadSVG()}
365
+ >
366
+ {t('button.export_chart_as', { type: 'svg' })}
367
+ </button>
368
+ </div>
369
+ </div>
370
+ </div>
310
371
  </LiteForm>
311
372
  </LiteContainer>
312
373
  }