techniq / layerchart

Composable Svelte chart components to build a wide range of visualizations
https://www.layerchart.com
MIT License
589 stars 11 forks source link

[Bars] Support scaleTime() #62

Open techniq opened 11 months ago

techniq commented 11 months ago

Instead of using scaleBand for a lot of our time-based Bar examples, Bar / Bars should be updated to support linear scales such as scaleLinear and scaleTime.

This should probably be handled within createDimensionGetter(), and likely be similar (if not exactly) to how Highlight does this.

If the same, we could probably consolidate the usage into common utils.

techniq commented 11 months ago

Continuation of some of the discussion in issue #54. (cc: @mattlangeman)

techniq commented 10 months ago

See also #21, particularly:

mattlangeman commented 8 months ago

FYI: I've started looking into this issue on a branch here: https://github.com/mattlangeman/layerchart/tree/temp-bar-timescales

The main issue I've run into so far is dealing with the last bar going off the edge of the chart. I was able to solve this by adding an extra day to the xDomain, but it doesn't seem ideal to need this all of the time.

Maybe it makes sense to add this a default in Chart.svelte if it is a barchart and using timeScale.

I also need a way to detect that it is a barchart using timeScale in order to shift the ticks in Axis.svelte

I think we need some type of interval setting or detection to determine if we are dealing with days/months/quarters/years, etc.

Note: I've only focused on the Columns/vertical bars for now.

Overview to todos:

barTimeScaleChartExample barTimeScaleChartExampeExtraDay

mattlangeman commented 8 months ago

I'm also wanting to add in support for multi-level axis for time scale data. I have some preliminary code, but definitely still needs work on abstracting and refining.

CleanShot 2024-01-28 at 14 52 59@2x
techniq commented 8 months ago

Some quick thoughts...

A way to detect if it's a barchart with timeScale (or some other way to know we need to shift ticks and adjust domain)

Currently checking for band scales (barchart) using isScaleBand().

For timeScale(), I don't see anything directly on the scale to indicate it...

but maybe if the .domain() are Date instances AND it's a continuous scale, that might work.

Basically need to change if x is band and y is time, or vice versa.

An interview setting or detection for day/month/quarter/year/???

You might take a look at getDuration in Svelte UX, along with getMinorTicks / getMajorTicks in ticks. I've been meaning to incorporate this for smarter date ticks as well. Lastly, Svelte UX has PeriodType.Day, PeriodType.Month, etc which are used for explicit date formatting. We've done a lot of work in the upcoming Svelte UX release that improves Intl support as well. I think we can improve the docs even more, but take a look at format.

A way to override domain to extend it so the last bar remains on the chart

the same would be helpful with using line with a step curve. I've manually added an extra data point in this situation as well, so having a util function to wrap <Chart data={duplicateLast(data)} /> or <Chart {data} duplicateLast /> (just thinking out loud, and not set on name).

// Add extra data point to fix last day/month
$: chartData = [
  ...data,
  {
    ...data[data.length - 1],
    date: endOfMonthFunc(data[data.length - 1]?.date),
  },
];

I'm also wanting to add in support for multi-level axis for time scale data. I have some preliminary code, but definitely still needs work on abstracting and refining.

I'm also wanting this. Observable Plot does a nice job of this (like Datawrapper)...

image

and zoomable-timeline has been something I've wanted to dig in more. Also semi-related - AutoChart

techniq commented 7 months ago

@mattlangeman bit of a hack/workaround, but today I wanted to add a daily volume chart to my Github Analysis site (bar chart under the area chart), and accomplished this by passing all the days within the extents using d3-time's .range()

https://github.com/techniq/github-analysis/blob/main/src/routes/stars/%2Blayout.svelte#L51-L52

image

This isn't the easiest example to see with a wide range. If I only show the last 50 points, you can see the band/scale better...

image

and using xNice will better align the area and bar charts (band ticks being in the middle of the day)

image

I still want to support scaleTime with Bars, and will continue to think about how to enable that, but this does seem to work by still leveraging scaleBand()

techniq commented 7 months ago

One thought I had to detect using a Bars was possibly passing <Chart xInterval={timeDay)> (or yInterval) similar to plot

techniq commented 4 months ago

One option (and maybe the best) is to pre-bin the data using d3's bin() along with either thresholdTime from LayerChart (for nicer date boundaries), or bin by d3-time interval (days, weeks, etc).

Bins by count

import { bin } from 'd3-array';
import { thresholdTime } from 'layerchart';

const = 10;
const binByTime = bin().thresholds(thresholdTime(count));
const data = binByTime(sourceData);

Bins by interval (days, weeks, etc)

import { bin } from 'd3-array';
import { timeDays, timeWeeks, timeMonths, ... } from 'd3-time';

const binByTime = bin().thresholds((data, min, max) => timeWeeks(min, max));
const data = binByTime(sourceData);

I've added some Histogram examples for these 2 approaches.