cleanup and clicking

This commit is contained in:
Moritz Jung 2025-10-07 13:02:06 +02:00
parent 6347f4caaf
commit a086b18a5e
16 changed files with 259 additions and 219 deletions

View file

@ -0,0 +1 @@
![[penguins.base]]

View file

@ -6,4 +6,4 @@ Low: 174.520004
Close: 176.820007
Adj Close: 176.140793
Volume: 28401400
---
---

View file

@ -6,4 +6,4 @@ Low: 175.070007
Close: 176.940002
Adj Close: 176.26033
Volume: 23774100
---
---

View file

@ -6,4 +6,4 @@ Low: 89.681427
Close: 90.76857
Adj Close: 84.457962
Volume: 94118500
---
---

View file

@ -6,4 +6,4 @@ Low: 101.559998
Close: 102.25
Adj Close: 95.614388
Volume: 68460000
---
---

View file

@ -6,4 +6,4 @@ culmen_depth_mm: 20.8
flipper_length_mm: 201
body_mass_g: 4300
sex: MALE
---
---

View file

@ -6,4 +6,4 @@ culmen_depth_mm: 19.8
flipper_length_mm: 207
body_mass_g: 4000
sex: MALE
---
---

View file

@ -6,4 +6,4 @@ culmen_depth_mm: 14.3
flipper_length_mm: 218
body_mass_g: 5700
sex: MALE
---
---

View file

@ -1,9 +1,10 @@
import type { BasesEntry, BasesQueryResult, QueryController, Value } from 'obsidian';
import type { BasesEntry, QueryController, Value } from 'obsidian';
import type { BasesPropertyId, ViewOption } from 'obsidian';
import { BasesView, DateValue, Events, NumberValue, StringValue } from 'obsidian';
import BarPlot from 'packages/obsidian/src/charts/BarPlot.svelte';
import LinePlot from 'packages/obsidian/src/charts/LinePlot.svelte';
import ScatterPlot from 'packages/obsidian/src/charts/ScatterPlot.svelte';
import { OBSIDIAN_COLOR_PALETTE, OBSIDIAN_DEFAULT_SINGLE_COLOR } from 'packages/obsidian/src/utils/utils';
import { mount, unmount } from 'svelte';
export const SCATTER_CHART_VIEW_TYPE = 'chart-scatter';
@ -14,7 +15,6 @@ export type ChartViewType = typeof SCATTER_CHART_VIEW_TYPE | typeof LINE_CHART_V
export const CHART_SETTINGS = {
X: 'x',
LABEL: 'label',
SHOW_PERCENTAGES: 'show-percentages',
SHOW_LABELS: 'show-labels',
MULTI_CHART: 'multi-chart-mode',
@ -24,9 +24,19 @@ export const CHART_SETTINGS = {
export type ProcessedData = {
x: number | Date | string;
y: number;
yProperty: BasesPropertyId;
label?: string;
group?: string;
/**
* This is always the value by which we color the data point.
* In group separated mode, this is the display name of the property.
* In property separated mode, this is the group by value.
*/
groupIndex: number;
/**
* This is always the chart to sort the data point into.
* In group separated mode, this is the group by value.
* In property separated mode, this is the property id.
*/
chartIndex: number;
file: string;
};
export interface ProcessedGroupEntry {
@ -49,120 +59,130 @@ export enum MultiChartMode {
PROPERTY = 'Separate by property',
}
export class DataWrapper {
readonly data: ProcessedGroupedData;
readonly properties: BasesPropertyId[];
readonly mode: MultiChartMode;
function sortDataByGroup(data: ProcessedData[]): ProcessedData[] {
return data.sort((a, b) => {
if (a.groupIndex != null && b.groupIndex != null) {
return a.groupIndex - b.groupIndex;
}
return 0;
});
}
constructor(data: ProcessedGroupedData, properties: BasesPropertyId[], mode: MultiChartMode) {
export abstract class AbstractDataWrapper<ChartId, GroupId> {
readonly view: ChartView;
readonly data: ProcessedData[];
readonly groupBySet: string[];
constructor(view: ChartView, data: ProcessedData[], groupBySet: string[]) {
this.view = view;
this.data = data;
this.properties = properties;
this.mode = mode;
if (this.data.grouped) {
this.data.groups.sort((a, b) => a.key.localeCompare(b.key));
}
this.groupBySet = groupBySet;
}
static empty(): DataWrapper {
return new DataWrapper({ grouped: false, entries: [] }, [], MultiChartMode.PROPERTY);
}
abstract getChartIdentifiers(): ChartId[];
abstract getGroupIdentifiers(): GroupId[];
getCharts(): string[] {
if (this.mode === MultiChartMode.GROUP) {
if (this.data.grouped) {
return this.data.groups.map(g => g.key);
} else {
return ['All'];
}
} else {
return this.properties;
}
}
abstract getChartName(chartIndex: number): string;
abstract getGroupName(groupIndex: number): string;
getChartName(chart: string, view: ChartView): string {
if (this.mode === MultiChartMode.GROUP) {
return chart;
} else {
const xProp = view.config.getAsPropertyId(CHART_SETTINGS.X);
return xProp ? view.config.getDisplayName(xProp) : 'Unknown';
}
}
getChartGroup(): string {
if (this.mode === MultiChartMode.GROUP && this.properties.length > 1) {
return 'group';
} else if (this.mode === MultiChartMode.PROPERTY && this.data.grouped) {
return 'group';
getChartGroupIdentifier(): (d: ProcessedData) => string {
if (this.hasMultipleGroups()) {
return d => this.getColorFromGroupIndex(d.groupIndex);
}
return 'var(--bases-charts-accent)';
return OBSIDIAN_DEFAULT_SINGLE_COLOR;
}
getColorFromGroupIndex(groupIndex: number): string {
return OBSIDIAN_COLOR_PALETTE[groupIndex % OBSIDIAN_COLOR_PALETTE.length];
}
hasMultipleGroups(): boolean {
return (this.mode === MultiChartMode.GROUP && this.properties.length > 1) || (this.mode === MultiChartMode.PROPERTY && this.data.grouped);
return this.getGroupIdentifiers().length > 1;
}
getFlat(chart: string, sorted: boolean = false): ProcessedData[] {
let data: ProcessedData[];
hasMultipleCharts(): boolean {
return this.getChartIdentifiers().length > 1;
}
if (this.mode === MultiChartMode.GROUP) {
if (this.data.grouped) {
const group = this.data.groups.find(g => g.key === chart);
data = group ? group.entries : [];
} else {
data = this.data.entries;
}
} else {
if (this.data.grouped) {
data = this.data.groups.flatMap(g => g.entries.filter(d => d.yProperty === chart));
} else {
data = this.data.entries.filter(d => d.yProperty === chart);
}
}
getFlat(chartIndex: number, sorted: boolean = false): ProcessedData[] {
const data = this.data.filter(d => d.chartIndex === chartIndex);
if (sorted) {
data = data.sort((a, b) => {
if (a.group != null && b.group != null) {
return a.group.localeCompare(b.group);
}
return 0;
});
return sortDataByGroup(data);
}
return data;
}
getStacked(chart: string): ProcessedData[] {
if (this.mode === MultiChartMode.GROUP) {
throw new Error('Stacked data is only available in property mode');
}
if (!this.data.grouped) {
return this.data.entries;
}
getStacked(chartIndex: number): ProcessedData[] {
const xMap = new Map<number | Date | string, number>();
const stackedData: ProcessedData[] = [];
for (const group of this.data.groups) {
for (const entry of group.entries.filter(d => d.yProperty === chart)) {
const prevY = xMap.get(entry.x) ?? 0;
const newY = prevY + entry.y;
stackedData.push({
...entry,
y: newY,
});
xMap.set(entry.x, newY);
for (const entry of this.data) {
if (entry.chartIndex !== chartIndex) {
continue;
}
const prevY = xMap.get(entry.x) ?? 0;
const newY = prevY + entry.y;
stackedData.push({
...entry,
y: newY,
});
xMap.set(entry.x, newY);
}
return stackedData;
}
}
export class GroupSeparatedData extends AbstractDataWrapper<string, BasesPropertyId> {
getChartIdentifiers(): string[] {
return this.groupBySet;
}
getGroupIdentifiers(): BasesPropertyId[] {
return this.view.data.properties;
}
getChartName(chartIndex: number): string {
return this.getChartIdentifiers()[chartIndex] ?? `Chart ${chartIndex + 1}`;
}
getGroupName(groupIndex: number): string {
const groupId = this.getGroupIdentifiers()[groupIndex];
return this.view.config.getDisplayName(groupId) ?? `Group ${groupIndex + 1}`;
}
}
export class PropertySeparatedData extends AbstractDataWrapper<BasesPropertyId, string> {
getChartIdentifiers(): BasesPropertyId[] {
return this.view.data.properties;
}
getGroupIdentifiers(): string[] {
return this.groupBySet;
}
getChartName(chartIndex: number): string {
const chartId = this.getChartIdentifiers()[chartIndex];
return this.view.config.getDisplayName(chartId) ?? `Chart ${chartIndex + 1}`;
}
getGroupName(groupIndex: number): string {
return this.getGroupIdentifiers()[groupIndex] ?? `Group ${groupIndex + 1}`;
}
}
export type DataWrapper = GroupSeparatedData | PropertySeparatedData;
export function emptyDataWrapper(view: ChartView): DataWrapper {
return new GroupSeparatedData(view, [], []);
}
export function parseValueAsNumber(value: Value | null): number | null {
if (!value) {
return null;
@ -245,54 +265,42 @@ export class ChartView extends BasesView {
}
processData(): DataWrapper {
const queryResult: BasesQueryResult | null = this.data;
const xField = this.config.getAsPropertyId(CHART_SETTINGS.X);
const labelField = this.config.getAsPropertyId(CHART_SETTINGS.LABEL);
const mode = this.config.get(CHART_SETTINGS.MULTI_CHART) ?? MultiChartMode.PROPERTY;
if (mode !== MultiChartMode.GROUP && mode !== MultiChartMode.PROPERTY) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
console.warn(`Invalid multi chart mode: ${mode}`);
return DataWrapper.empty();
return emptyDataWrapper(this);
}
if (!xField) {
return DataWrapper.empty();
return emptyDataWrapper(this);
}
if (this.isGrouped()) {
const data: ProcessedGroupedData = {
grouped: true,
groups: [],
};
const data: ProcessedData[] = [];
const groupBySet = this.data.groupedData.map(g => g.key?.toString()).filter(v => v != null);
groupBySet.sort();
for (const group of queryResult?.groupedData ?? []) {
const groupData: ProcessedGroupEntry = {
key: group.key!.toString(),
entries: [],
};
for (const entry of group.entries) {
const processedEntry = this.processEntry(entry, xField, labelField, group.key?.toString(), mode);
groupData.entries.push(...processedEntry);
}
data.groups.push(groupData);
for (const group of this.data?.groupedData ?? []) {
const groupKey = group.key?.toString();
let groupIndex: number;
if (groupKey == null) {
groupIndex = 0;
} else {
groupIndex = groupBySet.indexOf(groupKey);
}
return new DataWrapper(data, this.data.properties, mode);
for (const entry of group.entries) {
const processedEntry = this.processEntry(entry, xField, groupIndex, mode);
data.push(...processedEntry);
}
}
if (mode === MultiChartMode.GROUP) {
return new GroupSeparatedData(this, data, groupBySet);
} else {
const data: ProcessedGroupedData = {
grouped: false,
entries: [],
};
for (const entry of queryResult?.data ?? []) {
const processedEntry = this.processEntry(entry, xField, labelField, undefined, mode);
data.entries.push(...processedEntry);
}
return new DataWrapper(data, this.data.properties, mode);
return new PropertySeparatedData(this, data, groupBySet);
}
}
@ -300,35 +308,27 @@ export class ChartView extends BasesView {
return !(this.data.groupedData?.length === 1 && this.data.groupedData[0].key == null);
}
processEntry(
entry: BasesEntry,
xField: BasesPropertyId,
labelField: BasesPropertyId | null,
group: string | undefined,
mode: MultiChartMode,
): ProcessedData[] {
processEntry(entry: BasesEntry, xField: BasesPropertyId, groupIndex: number, mode: MultiChartMode): ProcessedData[] {
try {
const x = entry.getValue(xField);
const label = labelField ? entry.getValue(labelField) : null;
const xValue = parseValueAsX(x);
const labelStr = label?.toString();
if (xValue === null) {
return [];
}
const result: ProcessedData[] = [];
for (const prop of this.data.properties) {
for (let i = 0; i < this.data.properties.length; i++) {
const prop = this.data.properties[i];
const yValue = parseValueAsNumber(entry.getValue(prop));
const yName = this.config.getDisplayName(prop);
if (xValue !== null && yValue !== null) {
result.push({
x: xValue,
y: yValue,
yProperty: prop,
label: labelStr,
group: mode === MultiChartMode.GROUP ? yName : group,
groupIndex: mode === MultiChartMode.GROUP ? i : groupIndex,
chartIndex: mode === MultiChartMode.GROUP ? groupIndex : i,
file: entry.file.path,
});
}
}
@ -341,6 +341,20 @@ export class ChartView extends BasesView {
return [];
}
async openFile(filePath: string, newTab: boolean): Promise<void> {
const tFile = this.app.vault.getFileByPath(filePath);
if (!tFile) {
return;
}
const activeLeaf = this.app.workspace.getLeaf(newTab ? 'tab' : false);
if (activeLeaf) {
await activeLeaf.openFile(tFile, {
state: { mode: 'source' },
});
}
}
static getViewOptions(type: ChartViewType): ViewOption[] {
if (type === SCATTER_CHART_VIEW_TYPE) {
return ChartView.scatterViewOptions();
@ -372,13 +386,6 @@ export class ChartView extends BasesView {
filter: prop => !prop.startsWith('file.'),
placeholder: 'Property',
},
{
displayName: 'Label',
type: 'property',
key: CHART_SETTINGS.LABEL,
filter: prop => !prop.startsWith('file.'),
placeholder: 'Property',
},
];
}
@ -401,13 +408,6 @@ export class ChartView extends BasesView {
filter: prop => !prop.startsWith('file.'),
placeholder: 'Property',
},
{
displayName: 'Label',
type: 'property',
key: CHART_SETTINGS.LABEL,
filter: prop => !prop.startsWith('file.'),
placeholder: 'Property',
},
];
}
@ -420,13 +420,6 @@ export class ChartView extends BasesView {
filter: prop => !prop.startsWith('file.'),
placeholder: 'Property',
},
{
displayName: 'Label',
type: 'property',
key: CHART_SETTINGS.LABEL,
filter: prop => !prop.startsWith('file.'),
placeholder: 'Property',
},
{
displayName: 'Show labels',
type: 'toggle',

View file

@ -2,7 +2,7 @@
import { AxisX, AxisY, BarY, GridY, Plot, Text } from 'svelteplot';
import { CHART_SETTINGS, ChartView } from '../ChartView';
import { onMount } from 'svelte';
import { OBSIDIAN_COLOR_PALETTE, toCompactString } from '../utils';
import { OBSIDIAN_COLOR_PALETTE, toCompactString } from '../utils/utils';
import PlotGrid from './PlotGrid.svelte';
interface Props {
@ -29,11 +29,10 @@
</script>
<PlotGrid view={view}>
{#snippet chartSnippet({ data, chart, xName, group, height })}
{#snippet chartSnippet({ data, chartIndex, xName, groupFn, height })}
<Plot
color={{ legend: true, scheme: OBSIDIAN_COLOR_PALETTE as unknown as string }}
x={{ label: xName, type: 'band' }}
y={{ label: `↑ ${data.getChartName(chart, view)}`, tickFormat: show_percentages ? d => `${String(d)}%` : d => toCompactString(d) }}
y={{ label: `↑ ${data.getChartName(chartIndex)}`, tickFormat: show_percentages ? d => `${String(d)}%` : d => toCompactString(d) }}
height={height}
class="bases-charts-plot"
>
@ -41,10 +40,10 @@
<AxisY fill="var(--bases-charts-text)" stroke="var(--bases-charts-text)" opacity={1} />
<GridY stroke="var(--bases-charts-grid)" strokeOpacity={1} />
<BarY data={data.getFlat(chart)} x="x" y="y" fill={group} />
<BarY data={data.getFlat(chartIndex)} x="x" y="y" fill={groupFn} />
{#if show_labels}
<Text
data={data.getStacked(chart)}
data={data.getStacked(chartIndex)}
x="x"
y="y"
fill="var(--bases-charts-text)"

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { Line, Plot, GridX, GridY, AxisY, AxisX, Pointer, Text, Dot, RuleX } from 'svelteplot';
import { ChartView } from '../ChartView';
import { OBSIDIAN_COLOR_PALETTE, toCompactString } from '../utils';
import { OBSIDIAN_COLOR_PALETTE, toCompactString } from '../utils/utils';
import PlotGrid from './PlotGrid.svelte';
interface Props {
@ -12,25 +12,18 @@
</script>
<PlotGrid view={view}>
{#snippet chartSnippet({ data, chart, xName, isGrouped, group, height })}
{@const dataPoints = data.getFlat(chart, true)}
<Plot
grid
color={{ legend: isGrouped, scheme: OBSIDIAN_COLOR_PALETTE as unknown as string }}
x={{ label: xName }}
y={{ label: `↑ ${data.getChartName(chart, view)}` }}
height={height}
class="bases-charts-plot"
>
{#snippet chartSnippet({ data, chartIndex, xName, groupFn, height, setHoveredData })}
{@const dataPoints = data.getFlat(chartIndex, true)}
<Plot grid x={{ label: xName }} y={{ label: `↑ ${data.getChartName(chartIndex)}` }} height={height} class="bases-charts-plot">
<AxisX fill="var(--bases-charts-text)" stroke="var(--bases-charts-text)" opacity={1} />
<AxisY fill="var(--bases-charts-text)" stroke="var(--bases-charts-text)" opacity={1} />
<GridX stroke="var(--bases-charts-grid)" strokeOpacity={1} />
<GridY stroke="var(--bases-charts-grid)" strokeOpacity={1} />
<Line data={dataPoints} x="x" y="y" stroke="group" />
<Pointer data={dataPoints} x="x" z={group} maxDistance={50}>
<Line data={dataPoints} x="x" y="y" stroke={groupFn} />
<Pointer data={dataPoints} x="x" z={groupFn} maxDistance={50} onupdate={setHoveredData}>
{#snippet children({ data })}
<RuleX data={data} x="x" stroke="var(--bases-charts-muted)" />
<RuleX data={data} x="x" stroke="var(--bases-charts-grid-hover)" />
<Text data={data} x="x" y="y" fill="var(--bases-charts-text)" text={d => toCompactString(d.y)} lineAnchor="bottom" dy={-10} />
<Dot data={data} x="x" y="y" stroke="var(--bases-charts-text)" />
{/snippet}

View file

@ -1,12 +1,12 @@
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
import { CHART_SETTINGS, type ChartView, type DataWrapper } from '../ChartView';
import { type FullChartProps } from '../utils';
import { AbstractDataWrapper, CHART_SETTINGS, type ChartView, type DataWrapper } from '../ChartView';
import { OBSIDIAN_DEFAULT_SINGLE_COLOR, type FullChartProps } from '../utils/utils';
import PlotGridItem from './PlotGridItem.svelte';
interface Props {
view: ChartView;
chartSnippet: Snippet<[FullChartProps]>;
chartSnippet: Snippet<[FullChartProps<string, string>]>;
}
let { view, chartSnippet }: Props = $props();
@ -14,7 +14,7 @@
let data: DataWrapper | null = $state(null) as DataWrapper | null;
let xName: string = $state('');
let isGrouped: boolean = $derived(data?.hasMultipleGroups() ?? false);
let group: string = $derived(data?.getChartGroup() ?? 'var(--bases-charts-accent)');
let groupFn = $derived(data?.getChartGroupIdentifier() ?? OBSIDIAN_DEFAULT_SINGLE_COLOR);
function onUpdate() {
const xField = view.config?.getAsPropertyId(CHART_SETTINGS.X);
@ -33,17 +33,29 @@
});
</script>
<div class="bases-charts-plot-legend">
{#if data}
{#each data?.getGroupIdentifiers() ?? [] as _, groupIndex}
<div class="bases-charts-plot-legend-item">
<div class="bases-charts-plot-legend-color" style="--color: {data.getColorFromGroupIndex(groupIndex)}"></div>
<span class="bases-charts-plot-legend-label">{data.getGroupName(groupIndex)}</span>
</div>
{/each}
{/if}
</div>
<div class="bases-charts-plot-grid">
{#if data}
{#each data.getCharts() as chart}
{#each data.getChartIdentifiers() as _, chartIndex}
<PlotGridItem
view={view}
chartSnippet={chartSnippet}
chartProps={{
data,
chart,
chartIndex,
xName,
isGrouped,
group,
groupFn,
}}
></PlotGridItem>
{:else}
@ -57,10 +69,32 @@
<style>
.bases-charts-plot-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(var(--bases-charts-min-width), 1fr));
gap: var(--size-4-4);
width: 100%;
height: 100%;
padding: var(--size-4-4);
}
.bases-charts-plot-legend {
display: flex;
flex-wrap: wrap;
gap: var(--size-4-3);
padding-inline: var(--size-4-4);
padding-top: var(--size-4-4);
}
.bases-charts-plot-legend-item {
display: flex;
align-items: center;
gap: var(--size-2-2);
font-size: var(--font-small);
color: var(--bases-charts-text);
}
.bases-charts-plot-legend-color {
width: var(--size-4-4);
height: var(--size-4-4);
background-color: var(--color);
}
</style>

View file

@ -1,23 +1,44 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import type { ChartProps, FullChartProps } from '../utils';
import type { ChartProps, FullChartProps } from '../utils/utils';
import type { ChartView, ProcessedData } from '../ChartView';
interface Props {
chartProps: ChartProps;
chartSnippet: Snippet<[FullChartProps]>;
view: ChartView;
chartProps: ChartProps<string, string>;
chartSnippet: Snippet<[FullChartProps<string, string>]>;
}
let { chartProps, chartSnippet }: Props = $props();
let { view, chartProps, chartSnippet }: Props = $props();
let width = $state(0);
let height = $state(0);
let hoveredData: ProcessedData[] = $state([]);
function setHoveredData(data: ProcessedData[]) {
hoveredData = data;
}
function onClick(e: MouseEvent) {
if (hoveredData.length < 1) {
return;
}
const newTab = e.ctrlKey || e.metaKey;
const filePath = hoveredData[0].file;
view.openFile(filePath, newTab);
}
</script>
<div class="bases-charts-plot-grid-item" bind:clientWidth={width} bind:clientHeight={height}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="bases-charts-plot-grid-item" bind:clientWidth={width} bind:clientHeight={height} onclick={onClick}>
{@render chartSnippet({
...chartProps,
width,
height,
setHoveredData,
})}
</div>
@ -26,6 +47,7 @@
position: relative;
width: 100%;
height: 100%;
min-height: 300px;
min-height: var(--bases-charts-min-height);
min-width: var(--bases-charts-min-width);
}
</style>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { AxisX, AxisY, Dot, GridX, GridY, Plot, Pointer, Text } from 'svelteplot';
import { ChartView } from '../ChartView';
import { OBSIDIAN_COLOR_PALETTE, toCompactString } from '../utils';
import { toCompactString } from '../utils/utils';
import PlotGrid from './PlotGrid.svelte';
interface Props {
@ -12,26 +12,19 @@
</script>
<PlotGrid view={view}>
{#snippet chartSnippet({ data, chart, xName, isGrouped, group, height })}
{@const dataPoints = data.getFlat(chart)}
<Plot
grid
color={{ legend: isGrouped, scheme: OBSIDIAN_COLOR_PALETTE as unknown as string }}
x={{ label: xName }}
y={{ label: `↑ ${data.getChartName(chart, view)}` }}
height={height}
class="bases-chart"
>
{#snippet chartSnippet({ data, chartIndex, xName, groupFn, height, setHoveredData })}
{@const dataPoints = data.getFlat(chartIndex)}
<Plot grid x={{ label: xName }} y={{ label: `↑ ${data.getChartName(chartIndex)}` }} height={height} class="bases-charts-plot">
<AxisX fill="var(--bases-charts-text)" stroke="var(--bases-charts-text)" opacity={1} />
<AxisY fill="var(--bases-charts-text)" stroke="var(--bases-charts-text)" opacity={1} />
<GridX stroke="var(--bases-charts-grid)" strokeOpacity={1} />
<GridY stroke="var(--bases-charts-grid)" strokeOpacity={1} />
<Dot data={dataPoints} x="x" y="y" stroke={group} />
<Pointer data={dataPoints} x="x" y="y" maxDistance={50}>
<Dot data={dataPoints} x="x" y="y" stroke={groupFn} />
<Pointer data={dataPoints} x="x" y="y" maxDistance={50} onupdate={setHoveredData}>
{#snippet children({ data })}
<Text data={data} x="x" y="y" fill="var(--bases-charts-text)" text={d => toCompactString(d.y)} lineAnchor="bottom" dy={-10} />
<Dot data={data} x="x" y="y" fill={group} stroke="var(--bases-charts-text)" />
<Dot data={data} x="x" y="y" fill={groupFn} stroke="var(--bases-charts-text)" />
{/snippet}
</Pointer>
</Plot>

View file

@ -1,8 +1,10 @@
body {
--bases-charts-text: var(--text-normal);
--bases-charts-muted: var(--color-base-60);
--bases-charts-grid: var(--color-base-30);
--bases-charts-grid: var(--background-modifier-border);
--bases-charts-grid-hover: var(--background-modifier-border);
--bases-charts-accent: var(--color-accent);
--bases-charts-min-width: 350px;
--bases-charts-min-height: 350px;
}
.bases-charts-plot {

View file

@ -1,4 +1,4 @@
import type { DataWrapper } from 'packages/obsidian/src/ChartView';
import type { AbstractDataWrapper, ProcessedData } from 'packages/obsidian/src/ChartView';
export function toCompactString(datum: number | string | symbol | boolean | Date | null | undefined): string {
if (datum == null) {
@ -30,15 +30,18 @@ export const OBSIDIAN_COLOR_PALETTE = [
'var(--color-pink)',
];
export interface ChartProps {
data: DataWrapper;
chart: string;
export const OBSIDIAN_DEFAULT_SINGLE_COLOR = (_: unknown): string => 'var(--bases-charts-accent)';
export interface ChartProps<ChartId, GroupId> {
data: AbstractDataWrapper<ChartId, GroupId>;
chartIndex: number;
xName: string;
isGrouped: boolean;
group: string;
groupFn: (d: ProcessedData) => string;
}
export type FullChartProps = ChartProps & {
export type FullChartProps<ChartId, GroupId> = ChartProps<ChartId, GroupId> & {
width: number;
height: number;
setHoveredData: (data: ProcessedData[]) => void;
};