ObservedObserver / pivot-chart

light and fast implementation of web pivot table / pivot chart components.
https://pivot-chart.vercel.app/
MIT License
98 stars 25 forks source link
business-intelligence chart cube data-exploration data-visualization eda excel olap pivot-chart pivot-table pivot-tables react tableau typescript visualization

English | 简体中文

Pivot-Chart

Pivot-Chart is a comprehensive data visualization tool that enhances pivot tables with visualizations and charts, offering a more intuitive and informative way to analyze your data. With Pivot-Chart, you can elevate your data analysis and take a new approach to exploring your data. Unlike other pivot-table + vis solution, pivot chart retains nest multi-level aggregation feature of pivot table, which allows you to compare between different aggregation levels and make it easier to drill down and roll up.

Integrating Pivot-Chart into your data applications is easy, simply import the npm package.

While this is a proof-of-concept project, your feedback and suggestions are valued and appreciated.

pivot-chart also provide with basic pivot table components for you to build your web apps, you can regard pivot table as a member in the subset of pivot charts.

Demo

Online Demo

Features

feature demo(gif)
basic expandable nest/cross table basic expandable nest/cross table.gif
custom aggregator of measures ustom aggregator of measures.gif
different visualization type different visualization type.gif

Usage


install npm package.

npm i --save pivot-chart

# or

yarn add pivot-chart


basic usage.

import { PivotChart } from 'pivot-chart';

function App () {
  return <PivotChart
    visType={visType}
    dataSource={data}
    rows={rows}
    columns={columns}
    measures={measures} 
    />
}




demo above can be run locally with

# init development environment
yarn workspace react-pivot-table initenv
# start dev server
yarn workspace react-pivot-table dev

API

Types

Type Desc
Field
- id 
- name 
- aggName  aggregator's name.
- cmp <(a: any, b: any) => number>
Measure extends Field
- aggregator aggregator function.
- minWidth 
- formatter  => number
string ReactNode
VisType currently support number , bar , line , scatter .
Record a plain javascript object
DataSource Record[] , Array of Record.
QueryNode
- dimCode 
- dimValue 
QueryPath <QueryNode[]>.
example: [{dimCode: 'Sex', dimValue: 'male'}, {dimCode: 'Age', dimValue:'*'}]  


common

common interface are those used in both async pivot table and sync pivot table.


example:

import React, { useEffect, useState, useMemo } from 'react';
import ReactDOM from 'react-dom';
import { getTitanicData } from './mock';
import { ToolBar, PivotChart, DragableFields, Aggregators, DataSource, VisType, DraggableFieldState } from '../src/index';

const { dataSource, dimensions, measures } = getTitanicData();
const fields = dimensions.concat(measures).map(f => ({ id: f, name: f }));

const initDraggableState: DraggableFieldState = {
  fields: [],
  rows: [],
  columns: [],
  measures: []
};

function App () {
  const [data, setData] = useState<DataSource>([]);
  const [fstate, setFstate] = useState<DraggableFieldState>(initDraggableState)
  const [visType, setVisType] = useState<VisType>('number');
  useEffect(() => {
    setData(dataSource);
  }, [])
  const measures = useMemo(() => fstate['measures'].map(f => ({
    ...f,
    aggregator: Aggregators[(f.aggName || 'sum') as keyof typeof Aggregators]
  })), [fstate['measures']]);
  return <div>
    <DragableFields onStateChange={(state) => {setFstate(state)}} fields={fields} />
    <ToolBar visType={visType} onVisTypeChange={(type) => { setVisType(type) }} />
    <PivotChart visType={visType} dataSource={data} rows={fstate['rows']} columns={fstate['columns']} measures={measures} />
  </div>
}

ReactDOM.render(<App />, document.getElementById('root'))

AsyncPivotTable (WIP)

function AsyncApp () {

  const [data, setData] = useState<DataSource>([]);
  const [fields, setFields] = useState<Field[]>([]);
  const [fstate, setFstate] = useState<DraggableFieldState>(initDraggableState)
  const [visType, setVisType] = useState<VisType>('number');
  useEffect(() => {
    const { dataSource, dimensions, measures } = getTitanicData();
    setData(dataSource);
    const fs: Field[] = [...dimensions, ...measures].map((f: string) => ({ id: f, name: f }));
    setFields(fs);
  }, [])
  const measures = useMemo(() => fstate['measures'].map(f => ({
    ...f,
    aggregator: Aggregators[(f.aggName || 'sum') as keyof typeof Aggregators],
    minWidth: 100,
    formatter: f.id === 'Survived' && ((val: any) => `${val} *`)
  })), [fstate['measures']]);
  const cubeQuery = useCallback(async (path: QueryPath, meas: string[]) => {
    const ops = measures.filter(m => meas.includes(m.id)).map(m => m.aggName || 'sum');
    return TitanicCubeService(path.map(p => p.dimCode), meas, ops);
  }, [measures])
  return <div>
    <DragableFields onStateChange={(state) => {setFstate(state)}} fields={fields} />
    <ToolBar visType={visType} onVisTypeChange={(type) => { setVisType(type) }} />
    <AsyncPivotChart
      visType={visType}
      rows={fstate['rows']}
      columns={fstate['columns']}
      async
      defaultExpandedDepth={{
        rowDepth: 20,
        columnDepth: 20
      }}
      cubeQuery={cubeQuery}
      measures={measures} />
  </div>
}

Theme

interface ThemeConfig {
  root?: {
    display?: boolean,
    label?: string
  },
  summary?: {
    label?: string
  },
  table?: {
    thead?: {
      backgroundColor?: string;
      color?: string;
    }
    borderColor?: string;
    color?: string;
  }
}
// default config
const THEME_CONFIG: ThemeConfig = {
  root: {
    display: true,
    label: 'All'
  },
  summary: {
    label: '(total)'
  },
  table: {
    thead: {
      backgroundColor: '#E9EDF2',
      color: '#5A6C84'
    },
    borderColor: '#DFE3E8',
    color: '#333333'
  }
};
import React, { useEffect, useState, useMemo } from 'react';
import ReactDOM from 'react-dom';
import { getTitanicData } from './mock';
import { ToolBar, PivotChart, DragableFields, Aggregators, DataSource, VisType, DraggableFieldState, Theme } from '../src/index';

const { dataSource, dimensions, measures } = getTitanicData();
const fields = dimensions.concat(measures).map(f => ({ id: f, name: f }));

const initDraggableState: DraggableFieldState = {
  fields: [],
  rows: [],
  columns: [],
  measures: []
};

Theme.registerTheme({
  root: {
    display: true,
    label: 'root'
  },
  summary: {
    label: '(total)'
  }
})

function App () {
  const [data, setData] = useState<DataSource>([]);
  const [fstate, setFstate] = useState<DraggableFieldState>(initDraggableState)
  const [visType, setVisType] = useState<VisType>('number');
  useEffect(() => {
    setData(dataSource);
  }, [])
  const measures = useMemo(() => fstate['measures'].map(f => ({
    ...f,
    aggregator: Aggregators[(f.aggName || 'sum') as keyof typeof Aggregators]
  })), [fstate['measures']]);
  return <div>
    <DragableFields onStateChange={(state) => {setFstate(state)}} fields={fields} />
    <ToolBar visType={visType} onVisTypeChange={(type) => { setVisType(type) }} />
    <PivotChart visType={visType} dataSource={data} rows={fstate['rows']} columns={fstate['columns']} measures={measures} />
  </div>
}

ReactDOM.render(<App />, document.getElementById('root'))

Common Question

SyncPivotChart vs. AsyncPivotChart ?

Sync Pivot Chart does all cube computaion in frontend.(In future, it may do those work in webworker and it will seems to be async).
Async Pivot Chart can use cube query from server or customed implementation(either on server or browser, async or sync), but developer need to figure out how to speed up those by themsleves.