Skip to content

initial POC implementation of theming #59

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

Closed
wants to merge 3 commits into from

Conversation

jamesscottbrown
Copy link
Contributor

@jamesscottbrown jamesscottbrown commented May 23, 2025

We've been using Observable Plot for a number of projects and encountered some frustruations.

Some of these are already addressed by SveltePlot - for example, it makes it much easier to add event handlers to marks.

However, one thing that SveltePlot doens't currently address is theming (beyond those options that can be set as Default options).

We would mostly use this to apply our individual house style to plots, but other users might like to be able to select a theme from a list (similar to how ggthemes allows restyling of ggplot2 plots).

Out first approach was to define a number of objects containing default styling, and then spread these into the plot specifications. For example:

Plot.dot(penguins, {
	...defaultDotOptions,
	x: "culmen_length_mm", 
	y: "culmen_depth_mm"
}).plot()

The defaultDotOptions object would set a default dot size, color, and opacity, and remove the stroke.

However, this was a bit cumbersome, and having to explicitly include defaults seemed to partially default the purpose of defaults. We therefore wrote a Plot object that was a wrapper around the Plot object exported by @observablehq/plot, and replaced function with wrappers than first applied the defaults. The code was similar to:

import * as ObservablePlot from '@observablehq/plot';

export const Plot = {  
  ...ObservablePlot,  

  dot: (data, options) => {  
   return ObservablePlot.dot(data, { ...defaultDotOptions, ...options });  
  },

  // and so on for the other marks that we want to apply defaults to
}

This worked, but having to use our own wrapper function rather directly using the function from the upstream isn't ideal.

SveltePlot provides the potential for an alternative approach: providing a theme object to the <Plot> component as a prop, and providing it in a context so that the defaults can be used by each mark component.

This draft PR is a minimal example of this approach - it modifies enough component to make the effect of applying a theme to the charts on the "Why SveltePlot" page obvious, but without spending unnecessary time on implementation before consulting on the general approach.

@gka, what do you think of this general idea?

Copy link

netlify bot commented May 23, 2025

Deploy Preview for svelteplot ready!

Name Link
🔨 Latest commit 8e134b2
🔍 Latest deploy log https://app.netlify.com/projects/svelteplot/deploys/683093db50eda100085a1ee3
😎 Deploy Preview https://deploy-preview-59--svelteplot.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@jamesscottbrown
Copy link
Contributor Author

Preview of page with demo theme applied: https://deploy-preview-59--svelteplot.netlify.app/why-svelteplot

Page with defaults: https://svelteplot.dev/why-svelteplot

Copy link
Contributor

@gka gka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the general usefulness of this feature. But I don't like the idea of having defaults and theme coexisting. In your implementation, the theme really just provides default props for marks, right?

One thing I am already unhappy with in SveltePlot is how the defaults have to come up with their own naming schema, such as tickSize, which is essentially just marks.axisX.tickSize. Or there's the default graticuleStep which is then used as marks.graticule.step.

So how about we extent the existing defaults context with objects for each mark type, like

setContext('svelteplot/defaults', {
  line: {
    strokeWidth: 2
  },
  dot: {
    r: 4
  },
  graticule: {
    step: 10
  }
});

and then we spread these defaults into the mark props. But we need to do it in a way that doesn't break reactivity.

So for the Line mark it would look like this:

// create defaults from context + local defaults
const DEFAULTS = {
    curve = 'auto',
    tension = 0,
    canvas = false,
    class = null,
    lineClass = null,
    ...getContext('svelteplot/defaults').line
};

// I prefer `let` for props and state
let markProps = $props(); 

// this must be a $derived, otherwise it's not reactive!
const {
    data = [{}],
    curve,
    tension,
    text,
    canvas = false,
    class: className,
    lineClass,
    ...options
} = $derived({ ...DEFAULTS, ...markProps };

I think this would be much nicer than the existing defaults, and it should cover everything that your theme approach would cover, right?

What do you think?

@jamesscottbrown
Copy link
Contributor Author

Yes, I think that merging defaults and theme into one unified object with a nice structure like this would be a good idea.

@gka
Copy link
Contributor

gka commented May 25, 2025

I'll open a PR that cleans up the mark props (and exports them) so we can reuse them in the defaults type.

-> #63

@gka
Copy link
Contributor

gka commented May 26, 2025

let's continue the discussion in #66

@gka gka closed this May 26, 2025
@gka gka mentioned this pull request Jun 3, 2025
37 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy