-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(editor): add "Chart View" to display items as a chart #12677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: canary
Are you sure you want to change the base?
Changes from 5 commits
85d3f39
7c1369f
bfea0b7
1df03a4
ddbfdac
a0141fe
ef5281f
df2a759
62a7e6c
107e9bf
38f4b37
f162ff4
a908dd1
c5da0e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,54 @@ | ||||||||||
// AFFiNE/blocksuite/affine/data-view/src/view-presets/chart/chart-view-manager.ts | ||||||||||
|
||||||||||
import { computed, type ReadonlySignal } from '@preact/signals-core'; | ||||||||||
import { SingleViewBase } from '../../core/view-manager/single-view.js'; | ||||||||||
import type { ViewManager } from '../../core/view-manager/view-manager.js'; | ||||||||||
import type { ChartViewData } from './define.js'; | ||||||||||
import type { Cell } from '../../core/view-manager/cell.js'; | ||||||||||
|
||||||||||
/** | ||||||||||
* ChartSingleView manages the “Chart View” data. It computes, in a reactive | ||||||||||
* signal, a mapping from each category value to its row‐count. | ||||||||||
*/ | ||||||||||
export class ChartSingleView extends SingleViewBase<ChartViewData> { | ||||||||||
Check failure on line 13 in blocksuite/affine/data-view/src/view-presets/chart/chart-view-manager.ts
|
||||||||||
/** | ||||||||||
* categoryCounts$ is a computed signal that returns an object: | ||||||||||
* { [categoryValue: string]: number } | ||||||||||
* For each row in the datasource, it reads the cell value of | ||||||||||
* `categoryPropertyId` and increments the corresponding count. | ||||||||||
*/ | ||||||||||
readonly categoryCounts$: ReadonlySignal<Record<string, number>> = computed(() => { | ||||||||||
const data = this.data$.value; | ||||||||||
const categoryProp = data?.categoryPropertyId; | ||||||||||
if (!categoryProp) { | ||||||||||
return {}; // no category property selected → no data | ||||||||||
} | ||||||||||
|
||||||||||
// Get all existing rows in this view | ||||||||||
const rows = this.rows$.value; // array of Row objects | ||||||||||
const counts: Record<string, number> = {}; | ||||||||||
|
||||||||||
rows.forEach(row => { | ||||||||||
// For each row, get/create the cell in the chosen property | ||||||||||
const cell: Cell = this.cellGetOrCreate(row.rowId, categoryProp); | ||||||||||
// We assume the cell’s JSON value is a string (e.g. “TODO” or “Complete”) | ||||||||||
const raw = cell.jsonValue$.value as unknown; | ||||||||||
const category = typeof raw === 'string' && raw.length > 0 ? raw : 'Undefined'; | ||||||||||
counts[category] = (counts[category] || 0) + 1; | ||||||||||
}); | ||||||||||
|
||||||||||
return counts; | ||||||||||
}); | ||||||||||
|
||||||||||
/** | ||||||||||
* Overrides propertyGetOrCreate only if you need custom Property handling. | ||||||||||
* For Chart, we only group by an existing property; we do not need extra property logic. | ||||||||||
*/ | ||||||||||
override propertyGetOrCreate(propertyId: string) { | ||||||||||
return super.propertyGetOrCreate(propertyId); | ||||||||||
} | ||||||||||
NorkzYT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
constructor(viewManager: ViewManager, viewId: string) { | ||||||||||
super(viewManager, viewId); | ||||||||||
} | ||||||||||
Comment on lines
+88
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove unnecessary constructor. The constructor only calls the superclass constructor without any additional logic. - constructor(viewManager: ViewManager, viewId: string) {
- super(viewManager, viewId);
- } 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (1.9.4)[error] 88-93: This constructor is unnecessary. Unsafe fix: Remove the unnecessary constructor. (lint/complexity/noUselessConstructor) 🤖 Prompt for AI Agents
|
||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// AFFiNE/blocksuite/affine/data-view/src/view-presets/chart/define.ts | ||
|
||
import type { BasicViewDataType } from '../../core/view/data-view.js'; | ||
import { viewType } from '../../core/view/data-view.js'; | ||
import { ChartSingleView } from './chart-view-manager.js'; | ||
|
||
export const chartViewType = viewType('chart'); | ||
|
||
export type ChartViewData = BasicViewDataType< | ||
typeof chartViewType.type, | ||
{ | ||
/** Property ID to group rows by (e.g. a “status” property). */ | ||
categoryPropertyId?: string; | ||
} | ||
>; | ||
|
||
export const chartViewModel = chartViewType.createModel<ChartViewData>({ | ||
defaultName: 'Chart View', | ||
dataViewManager: ChartSingleView, | ||
defaultData: viewManager => { | ||
// By default, pick the first property in the datasource as the category field (if any) | ||
const allProps = viewManager.dataSource.properties$.value; | ||
return { | ||
mode: 'chart', | ||
categoryPropertyId: allProps.length > 0 ? allProps[0] : undefined, | ||
}; | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// AFFiNE/blocksuite/affine/data-view/src/view-presets/chart/effect.ts | ||
|
||
import { ChartViewUI } from './pc/chart-view-ui.js'; | ||
|
||
export function chartEffects() { | ||
customElements.define('dv-chart-view-ui', ChartViewUI); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// AFFiNE/blocksuite/affine/data-view/src/view-presets/chart/index.ts | ||
|
||
export * from './define.js'; | ||
export * from './chart-view-manager.js'; | ||
export * from './renderer.js'; | ||
export * from './effect.js'; | ||
export * from './styles.js'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// AFFiNE/blocksuite/affine/data-view/src/view-presets/chart/pc/chart-view-ui-logic.ts | ||
|
||
import { DataViewUILogicBase } from '../../../core/view/data-view-base.js'; | ||
import type { ChartSingleView } from '../chart-view-manager.js'; | ||
import { signal } from '@preact/signals-core'; | ||
import { ChartViewUI } from './chart-view-ui.js'; | ||
import type { Chart } from 'chart.js'; | ||
|
||
/** | ||
* ChartViewUILogic bridges the view‐layer (ChartSingleView) and the UI (ChartViewUI). | ||
* We hold onto a reactive signal `ui$` so our controllers can mount custom elements, | ||
* and we keep `chartInstance` to manage Chart.js. | ||
*/ | ||
export class ChartViewUILogic extends DataViewUILogicBase<ChartSingleView, unknown> { | ||
Check failure on line 14 in blocksuite/affine/data-view/src/view-presets/chart/pc/chart-view-ui-logic.ts
|
||
/** Holds the reference to the rendered ChartViewUI element. */ | ||
ui$ = signal<ChartViewUI>(); | ||
|
||
/** Once the chart is drawn, we keep this to update or destroy as needed. */ | ||
chartInstance: Chart<'doughnut', number[], string> | null = null; | ||
|
||
/** The `render` function used by DataView to instantiate the UI. */ | ||
renderer = (ChartViewUI as any); | ||
NorkzYT marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
/** Clean up the Chart instance when this logic is disposed. */ | ||
override onHostDisconnected(): void { | ||
Check failure on line 25 in blocksuite/affine/data-view/src/view-presets/chart/pc/chart-view-ui-logic.ts
|
||
if (this.chartInstance) { | ||
this.chartInstance.destroy(); | ||
this.chartInstance = null; | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.