Giter Site home page Giter Site logo

mhkeller / layercake Goto Github PK

View Code? Open in Web Editor NEW
1.2K 16.0 30.0 32.32 MB

graphics framework for sveltejs

Home Page: https://layercake.graphics

License: MIT License

JavaScript 36.97% Svelte 62.01% CSS 0.64% HTML 0.28% Shell 0.10%
sveltejs graphics dataviz layers cake dessert delicious layercake charts svelte

layercake's Introduction

Layer Cake layercake-logo

a framework for mostly-reusable graphics with svelte

Tests badges npm version npm

๐Ÿฐ See examples ๐Ÿฐ Read the guide ๐Ÿฐ API docs ๐Ÿฐ View the Component Gallery ๐Ÿฐ Try the starter template

Svelte versions

Works Svelte 3 and Svelte 4

Install

npm install --save layercake

Example

<script>
  // The library provides a main wrapper component
  // and a bunch empty layout components...
  import { LayerCake, Svg, Html, Canvas } from 'layercake';

  // ...that you fill with your own chart components,
  // that live inside your project and which you
  // can copy and paste from here as starting points.
  import AxisX from './components/AxisX.svelte';
  import AxisY from './components/AxisY.svelte';
  import Line from './components/Line.svelte';
  import Scatter from './components/Scatter.svelte';
  import Labels from './components/Labels.svelte';

  const data = [{ x: 0, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 3 }];
</script>

<style>
  .chart-container {
    width: 100%;
    height: 500px;
  }
</style>

<div class="chart-container">
  <LayerCake
    x='x'
    y='y'
    {data}
  >
    <Svg>
      <AxisX/>
      <AxisY/>
      <Line color='#f0c'/>
    </Svg>

    <Canvas>
      <Scatter color='#0fc'/>
    </Canvas>

    <Html>
      <Labels/>
    </Html>
  </LayerCake>
</div>

License

MIT

layercake's People

Contributors

benmccann avatar chris-creditdesign avatar connorrothschild avatar dependabot[bot] avatar jtrim-ons avatar mhkeller avatar rgieseke avatar sawyerclick avatar techniq avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

layercake's Issues

LayerCake and SSR?

I haven't tried to do it, but it seems like it wouldn't work since LayerCake depends on a DOM element. Maybe there is some way around this that I don't know about.

Best name for scaled SVG layer component?

On the ssr-features branch there's a new SVG component that follows Rich's pancake styling to allow for properly scaled SVG charts rendered server-side and scaled with Viewbox.

I called it SvgSsr for now but it's not the best name. It's a bit obscure from what it actually is doing and I don't really like typing it. Something like ScaledSvg makes more sense or SvgScaled if it seems more consistent to have Svg come first.

scaleLog example

Hey @mhkeller thanks again for handling a steady stream of questions from me... What is the "right" way to set a given axis to a scaleLog? I see scaleLinear and scaleSqrt are set as defaults in defaultScales.js and the scale types seem to be passed to the Axis components as, like, yScale and xScale with getContext('LayerCake'). Does this have to do with the availability of a nice function in createScale? Should I be trying to set this in /components/AxisY.svelte ?

xScale error

Hi, I am getting a strange error:

<script>
	import { data, e2eData } from '../../stores/datastore.js';

	import { LayerCake, Svg, flatten, calcExtents, Html } from 'layercake';
	import {
		scaleLinear,
		scaleBand,
		scaleTime,
		scaleDiverging,
		scaleOrdinal,
		scaleSequential
	} from 'd3-scale';

	import * as aq from 'arquero';
	import AxisX from '../layercake/AxisX.svelte';
	import AxisY from '../layercake/AxisY.svelte';
	import Tooltip from '../layercake/custom/MyTooltip.svelte';

	import Heatmap from '../layercake/custom/Heatmap.svelte';

	const margin = { top: 10, right: 10, bottom: 10, left: 10 };
	const tFormat = timeFormat('%d-%b-%y');

	let groupedData;


	let width = 1000;
	let height = 1000;

	let xScale;
	let xDomain;
	let yScale;
	let zScale;
	let zDomain;
	let yDomain;
	let xRange;
	let zRange;

	const dt = $e2eData
		.groupby('L2_Process', 'Anchoring_Function')
		.count()
		.filter((d) => d.Anchoring_Function !== null && d.L2_Process !== null)
		.orderby(aq.desc('count'));
	groupedData = dt.objects();

	/*******************
	 * Chart props
	 */
	// width = document.body.clientWidth - margin.left - margin.right;
	// height = document.body.clientHeight - margin.top - margin.bottom;
	// zScale = scaleOrdinal();
	// zDomain = groupedData;
	xDomain = dt.array('L2_Process');
	yDomain = dt.array('Anchoring_Function');

	// zRange = colors; // quantize(interpolateHcl('#f4e153', '#362142'), 38);
	xScale = scaleBand(dt.array('L2_Process'), [margin.left, width - margin.right]).round(true);
	yScale = scaleBand(yDomain, [margin.top, height - margin.bottom]).round(true);
	console.log(xScale('4.2 - Network Build'), yScale('NSRs'));

	/*****************/
</script>

<div style="height:{height}px;width:{width}px">
	<LayerCake
		x="L2_Process"
		y="Anchoring_Function"
		xScale
		yScale
		xDomain
		yDomain
		data={groupedData}
	>
		<Svg>
			<!-- <AxisY ticks={10} />
			<AxisX gridlines={true} /> -->
			<Heatmap />
		</Svg>
	</LayerCake>
</div>

<style>
	/*
		The wrapper div needs to have an explicit width and height in CSS.
		It can also be a flexbox child or CSS grid element.
		The point being it needs dimensions since the <LayerCake> element will
		expand to fill it.
	*/
	.chart-container {
		width: 100%;
		height: 100%;
	}
</style>

This is my heatmap file

<script>
	import { getContext } from 'svelte';
	// import { scaleQuantize } from 'd3-scale';
	// import { timeFormat } from 'd3-time-format';
	// import { timeDay } from 'd3-time';
	import { scaleLinear, scaleTime, scaleBand, scaleDiverging, scaleSequential } from 'd3-scale';
	import { interpolateRdBu, interpolateReds } from 'd3-scale-chromatic';
	import { range, extent } from 'd3-array';

	const { data, xGet, x, y, yGet, zGet, xScale, yScale, custom, width, height, xDomain } =
		getContext('LayerCake');
	// $data.forEach((d) => console.log(d));

	// let [min, max] = extent($data, (d) => d.count);

	// const zScale = scaleSequential(interpolateReds).domain([0, max]);

	// $data.sort((a, b) => a.date.localCompare(b.date));
	console.log('--', $yScale('NSRs'));
	// $: fillColor = (d) => {
	// 	console.log($xScale(d), $y(d));
	// 	// if (d.count < 0) {
	// 	// 	max = Math.max(-min, max);
	// 	// 	let newScale = scaleDiverging([-max, 0, max], (t) => interpolateRdBu(1 - t));
	// 	// 	return newScale(d.count);
	// 	// }
	// 	// return zScale(d.count);
	// };
</script>

<!-- <g>
	{#each $data as d}
		<rect
			x={$xGet(d)}
			y={$yGet(d)}
			width={$xScale.bandwidth()}
			height={$yScale.bandwidth() - 1}
			stroke="white"
			stroke-width="1"
		/>
	{/each}
</g> -->

I get two errors

  1. Unrecoverable error in : next update will trigger a full reload
  2. TypeError: $scale.copy is not a function

Any suggestions on what's going on?

SvelteKit 1.0.0-next.202 / Vite 2.7 issue

After upgrading to SvelteKit 1.0.0-next.202 which includes Vite 2.7, I receive the following when importing LayerCake:

10:36:36 PM [vite] Error when evaluating SSR module /src/lib/components/Chart.svelte:
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".svelte" for /Users/techniq/Documents/Development/open-source/layerchart/node_modules/layercake/src/LayerCake.svelte
    at new NodeError (node:internal/errors:371:5)
    at Object.file: (node:internal/modules/esm/get_format:72:15)
    at defaultGetFormat (node:internal/modules/esm/get_format:85:38)
    at defaultLoad (node:internal/modules/esm/load:13:42)
    at ESMLoader.load (node:internal/modules/esm/loader:303:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:230:58)
    at new ModuleJob (node:internal/modules/esm/module_job:63:26)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:244:11)
    at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:78:21)
    at async Promise.all (index 0)

While I'm not 100% certain, I believe the heuristic changed with Vite 2.7 to detect a svelte package, and the best tell now is to include a svelte property in package.json (along with main/exports). If using SvelteKit to build packages, running svelte-kit package now sets this for you. This PR along with the associated issue and code provides some additional info.

Sending a PR your way...

Dynamically calculate unique domain entries for .bandwidth scales

Currently, Layer Cake measures the extent of the data and the dimensions of the div to create scales. This is most useful to linear scales and all others require you to calculate your own domain, such as for scaleOrdinal. It would be nice if the user put in a scaleOrdinal, Layer Cake took the key for that dimension and calculated unique fields on its own. The user could override these just as they can do currently but it would be a nice way to get quickly up and running.

The blocker on this is that there's no easy way to determine what d3 scale is being used since they're all duck typed. You could write out logic to figure out what the scale is based on the methods but that seems easy to mess up and hard to maintain and test. You could pass a string for the scale but then you'd have to import all of d3-scale โ€“ย not sure if that would be tree-shook. This is on hold for now but it's an interesting idea if other progress can be made.

How to handle measuring extents of multi-series data

If you want, say, a multi-series line chart, the best data structure would be an array of arrays of objects with the same x and y keys like this

[
  [{x: 1, y: 10},{x: 2, y: 12},{x: 3, y: 15}],
  [{x: 1, y: 20},{x: 2, y: 22},{x: 3, y: 25}]
]

The problem is how can calcExtents properly measure the extents of this data?

Consider also this common data structure, which is similar to the output from d3.nest()

[
  {key: 'group1', values: [{x: 1, y: 10},{x: 2, y: 12},{x: 3, y: 15}]},
  {key: 'group2', values: [{x: 1, y: 10},{x: 2, y: 12},{x: 3, y: 15}]}
]

Option 1, include an option to flatten data one depth down

You could set a property in the store to flatten: true which could flatten this data, or have that be the default. This would also then help with D3 stack layouts.

Option 2, allow a custom flattening function

Let the user set their own function with flatten. This can be annoying though if the user doesn't really want to deal with flattening arrays of arrays properly

Option 3, allow pre-flattened data

This has the same problem as 3 but might also be a good idea in general in case the user is already flattening their data elsewhere or maybe it was loaded in flat and then they nested it, they can pass that in we avoid restructuring the data.

Thoughts

These aren't mutually exclusive and I could see an argument for including all of them since they offer convenience for casual users plus escape hatches if things get tricky.

This in general seems like a messy concept to have to handle but I'm not sure what a good solution is. The "do nothing" solution would be have calcExtents balk at anything that is array of arrays and require you to set your domains manually in that scenario. That could be problem though if you have a data structure like this below, which it seems would be silly to not support :

[
  [0, 1], [1, 4]
]

Fix domain padding

The logic for doing domain padding breaks when you have a large domain or large numbers, @aubergene pointed out. Started doing it on the fix-domain-padding branch. It needs more tests and would be good to detect how this changes for logarithmic and power scales. d3 scales are duck typed so have to detect what we're dealing with.

Demo request / map overlay question

Hi, I'm wondering how you'd suggest overlaying bubbles on top of a map layer? It seems like there could be a way to do this with https://layercake.graphics/example/MapLayered but don't immediately see how I would do this for a point layer in unprojected lat/lngs? Would I have to explicitly project them into viewport coordinates? Am not married to it being in canvas or svg, probably either would be fine. Am looking at tens of bubbles, probably not thousands? If this would need to be a new component, do you have any suggestions on implementation? Thanks in advance.

500 error in component docs

Great resource!

Looks like a recent update may have messed up some links required for documentation!

E.g., clicking on any of the chart thumbnails from https://layercake.graphics/components yields the following error:

500
Failed to fetch dynamically imported module: https://layercake.graphics/_app/pages/components/_slug_.svelte-998c6316.js
TypeError: Failed to fetch dynamically imported module: https://layercake.graphics/_app/pages/components/_slug_.svelte-998c6316.js

Replace quadratic-time algorithm for uniques()?

The uniques() function checks if each item is in an array of seen items, which I think leads to quadratic complexity overall. There are probably use cases such as a scatter plot with thousands of points where this would take a noticeable length of time.

Would it be worth making seen a Set? I think it would just need the following changes:

  • const seen = []; -> const seen = new Set();
  • seen.includes(computed) -> seen.has(computed)
  • seen.push(computed) -> seen.add(computed)

Compatibility with svelte-scroller

I'm opening this issue here for anyone having issues using Layer Cake inside of svelte-scroller. There's an issue in that library where the width is hardcoded at 0. Because Layer Cake will expand to fill the parent's dimensions, if you don't override this value somehow, Layer Cake won't have any width. That library should hopefully have a fix soon.

sveltejs/svelte-scroller#13

Dynamically set hard extents

Currently you can set a hard extent via xDomain={[0, 1]} but if you area also dynamically changing the xKey, then it becomes too much logic to set a hard extent or float to the computed extent.

If extents were exposed and accepted a key of extents the same as is used internally and outputted by calcExtents then you could start to do this more elegantly and also define which extents are used for each field

Allow for multiple x and y values

In stacked charts, you have multiple y values, representing the min and max. You should be able to return an array from an accessor so the user doesn't have to choose which one.

Changes needed:

  • calcExtent needs to loop over this array
  • getter functions as well

I don't think partialDomain needs any adjustment.

Remove d3-scale dependency

This should be doable as long as its replacement has the same API. Mostly the following methods:

  • invert
  • domain
  • range
  • nice

We'll need both scaleLinear and scaleSqrt

Update to use svelte-kit (includes adding types)

This is a work in progress on the kit branch. It includes typescript support and will close #49

TODO

  • Add types for typescript
  • Figure out how to export fewer functions
  • Excludes .DS_Store
  • Get example working
  • Add typescript types on context values (maybe slot values, too)?

Two plots on one <Canvas> causes update loop?

Thank you for writing LayerCake, it is a fantastic base to make custom plots on top of! We are moving from a more "component" based library and really enjoy the control Layer Cake provides. Particularly being able to trivally combine SVG (ease) with Canvas (high volume data layers).

That said, I have been playing with Canvas based plots today in anticipation of needing to plot a large amount of data soon. It seems that putting two components in one <Canvas> results in an update loop that never ends.

I wonder if I am doing something wrong?

You can see the issue in this REPL. It works as shown with two <Canvas> tags. You can see the issue by moving <Scatter /> into <Line />'s <Canvas> (while also commenting the two scaleLayer and clear rect lines from <Scatter />). WARNING: It will bind up your tab as soon as the REPL rebuilds.

Also, I wonder if multiple <Canvas> is better in the end anyway?

on:blur click events create an outline around svg paths

I noticed that adding an on:blur event handler creates an outline around the focused element, this results in strange behavior when clicking a path. See examples below:

  • non-SSR version
    example of non-ssr version
  • SSR version
    example of ssr version

To fix this issue on the Map component, I just added .feature-path:focus { outline: none; } to the <style/> block in the map component.
After reading through :focus' MDN docs, I'm not sure this is the best or most accessible solution. Thoughts? I'm happy to work on implementing a fix but I'm not entirely sure what the most accessible solution is.

Grouped Bar Chart

Thanks for the awesome library. I'm just beginning to experiment with Svelte (coming from React) and your library has been very useful to learn how to integrate with d3 (I've contributed to the React vx library in the past and considering porting the Text component to Svelte).

Looking at the guide and example, I see you have a xScale, yScale, zScale, and rScale for the scales. For the Stacked Column example, zScale is used to represent the stack.

I was curious, how would this translate to a Grouped Bar Chart or even further with a Grouped Stacked Bar Chart. I know typically the use of x0/x1 and y0/y1 scales are used. I didn't know if it made since to include at least x1Scale and y1Scale in LayerCake.

Rename `flatData` -> `dataForExtents`?

Per, #53 (comment), would it make more sense to rename the flatData to dataForExtents? It speaks more to what it's used for, which is a little less mysterious than describing what it must be.

This would be a breaking change but we can still support flatData for a little bit so things wouldn't actually break. I'm just about ready to publish version 6.0.0, which is the svelte-kit version. There are no other changes though โ€“ just figured it would be good to bump the major version in case it breaks a build process or something.

Typescript types?

Sorry if this is the wrong place to ask, but don't see any other community forum/chat.

I just wanted to verify that I am not missing user contributed Typescript types for Layer Cake that you might be aware of?

Detect padding from CSS or allow padding function

Currently we follow the D3 convention of passing in an object setting up the padding rules. This works fine but often you want to change the padding on mobile. We could do it where you pass in a function that gets the new width or maybe it's better to grab the CSS padding on the container itself and use that instead of having this object at all.

Zoom- and pannable SVG map

I'm a little new to interactive plotting on the web. If I wanted to plot a zoom- and pannable map of Germany and display some markers on it, could I use layercake? I'm guessing I would need to find a Topo/GeoJSON file for Germany and then figure out a way to convert lat/lng coordinates to SVG coordinates? Is layercake the right tool for this?

typeerror - calcExtents must be an Array

Hi,

I keep getting this error message that says 'TypeError: The first argument of calcExtents() must be an array.'

However my data I am passing passes the Array.isArray test and is an array of objects

Is there something I am missing?

Murali

Multipanel?

Hi, I am exploring how to best make multi-panel figures. I see you have one small multiple, but I want something that has much more freedom in placement than css grids.

I would want a main plotting area and then put Layercake graphs at certain coordinates (so you can recreated grids, but you can also just have e.g. the small multiples arranged in a circle.

I tried moving the graphs with xrange/yrange but Layercake seem to still consider the whole area as its playground and one can't have two Layercakes in one div? (terrible example here)

Do you have any hints how to achieve this, e.g. multiple graphs with separate coordinate systems within one svg? Or would I have to use one coordinate system from layer cake and then mak my own component to translate figure coordinates within that?

May be a LayerGroup component with translated coordinates and position in which one use the visualisation components...just a thought

SvelteKit / Vite support (works out of the box now, site and examples will be updated soon)

I recently moved to the latest SvelteKit based on Vite (1.0.0-next.50) and Layer Cake throws an error when imported:

500
Unexpected token 'export'

/Users/techniq/Documents/Development/playground/svelte-kit-layercake/node_modules/layercake/src/index.js:1
export { default as LayerCake } from './LayerCake.svelte';
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:979:16)
    at Module._compile (internal/modules/cjs/loader.js:1027:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
    at Module.require (internal/modules/cjs/loader.js:952:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at nodeRequire (/Users/techniq/Documents/Development/playground/svelte-kit-layercake/node_modules/vite/dist/node/chunks/dep-efe32886.js:68907:17)
    at ssrImport (/Users/techniq/Documents/Development/playground/svelte-kit-layercake/node_modules/vite/dist/node/chunks/dep-efe32886.js:68865:20)
    at eval (/src/lib/Chart.svelte:7:31)

It looks like it's being detected as commonjs instead of esm. I'm not 100% sure if the issue is with Layer Cake, Vite (as it worked with older SvelteKit based on Snowpack), SvelteKit recently adding type: module to package.json (I was on an older SvelteKit/Snowpack before this change) or something else.

I see Layer Cake has main and module defined in package.json, but not an export map which from my limited understanding is the best tell for ESM for Vite/Snowpack/etc.

I've created a repo to demonstrate the issue.

I just ran npm init svelte@next, added layercake and a simple chart example. When I initially ran this example last night the issue didn't present itself, but when I tried to identify what else was different between this example and my project just now, it show'd up. I've cleared and re-installed node_modules as well as the .svelte directly (trying to clear any cache) but the issue persists. I'm using node v14.14.0.

Tooltips on bar charts / d3-quadtree

Do you have suggestions / an example for implementing tooltips on a bar chart? My naive attempts to do it with d3-quadtree are not immediately working, probably because I'm passing it the wrong things. For a bar chart with <50 bars I'm sorta tempted to rewrite QuadTree.svelte's findItem, probably by ripping out d3-quadtree and replacing it with some elementary school math on the bar widths / heights. (the tear-it-down-and-replace-with-javascript-circa-1999 is an instinct I try to suppress, not always successfully). I can start putting these issues on the examples repo too if that makes more sense to you.

Unrelatedly I think I can contribute a HorizontalCategoricalMapScale component to the layer cake examples if that's at all useful, it's totally trivial to build, but the sorta thing that's handy to have all the same.

Add ssr={true|false} to control when charts are rendered

@mhkeller In the docs you mention:

The chart will actually render before the container width is measured and you'll get a flash of a chart before it adjusts itself. You can set a default width here that it will render to for that moment. It might be better to just now show the chart during this period but including this option for now. Defaults to 350.

I think this is the culprit of some of my graphs misbehaving with my current setup, depending on the browser I'm using. Do you have a way to not render the chart until after this flash?

iframe element for each chart

Hi, thanks for a great component. I am seeing some extra elements in my charts. They seem to be iframe elements and look like this:

<iframe style="display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: -1;" aria-hidden="true" tabindex="-1" src="about:blank"></iframe>

Is this something that is part of LayerCake. I also see it in the svelte-virtual-list component. Inspecting the LayerCake page also shows this iframe for each component.

LayerCake as a component

So, I took a very basic crack at making LayerCake a component.
https://github.com/zzolo/layercake-ssr-testing/tree/e362b7ea8de4713f869b32b9aa550a37dc6bb761
Building and running it is the same as described in #5 .

The main LayerCake component and parts are in layercake/. Note that I only implemented an SVG layer. Also, I just sort of implemented this from scratch just as an example and was not going for parity with LayerCake as far as options and methods.
https://github.com/zzolo/layercake-ssr-testing/tree/e362b7ea8de4713f869b32b9aa550a37dc6bb761/layercake

Then, in components/, you can see it implemented in Chart.html, and this gets called with data in Page.html.
https://github.com/zzolo/layercake-ssr-testing/tree/e362b7ea8de4713f869b32b9aa550a37dc6bb761/components

The main difference is that we have to pass data around components, so there's many {...props} attributes in components, where props is just the full state of the current component. For instance:

<SVG {...props} layers="{ svgLayers }" />
...
  computed: {
    props: state => state,
  }

This is not great, mostly because it's just kind of messy and it's not a two-way data source. But, I think in practice it is not so bad.

There's also an open question in LayerCake's current form and here on how might be the best way to handle events like clicking. I feel like the general idea is to create reusable components, such as an axis, and I am not sure of a good way to define a click handler as an option. This is probably just a Svelte limitation where the data in a component doesn't have access to this, like a method does.

Support padded domains for non-linear scales

The padding option works for linear scales but wouldn't work great for log or power scales since it's based on a proportion.

The related issue is perhaps this will be included as a basic D3 scale feature: d3/d3-scale#150

That issue also has information on Vega's implementation of this idea, which could serve as a blueprint.

Examples Broken

Hi!

Just getting into Svelte and Layer Cake, but it seems all but 2 examples when opened in the REPL are broken?

The data gets drawn but there's no responsiveness

They all throw

message: "Cannot read property 'defaultView' of null"
stack: TypeError: Cannot read property 'defaultView' of null

The only 2 that seem to still work are the circle packing examples.

Not sure if this is a problem with the REPL, or the library.

Expose layout variables to the user

It could be useful in some instances to allow the user access to the root svg or div element when constructing a layout. This could for an escape hatch if they want to use something like d3.zoom that operates on the root svg node. Some questions

  1. Should these be exposed as slot props or component props? The difference being slot props only exist within the child scope. Exposing them as component props requires a variable made in the parent to attach to.
  2. What should they be called? It's straightforward for svg layouts but html layouts doesn't lend itself to an intuitive name
    a. svg -> svg and g can both be exposed
    b. html -> html, div, layoutDiv?
    c. canvas -> canvas. The ctx is already exposed via a child svelte context object.

Current branch: 9350d3f

Improvements to calcExtents

Currently, the second argument to calcExtents is an array of objects defining a field and accessor. It might make more sense for this to be an object like

{
  x: d => d.x,
  y: d => d.y
}

Because the output of this function is a similarly structured object, it makes a bit more sense that the input matches the output. Also I think it would make it examples like the SmallMultiple wrapper be a bit easier where you wouldn't have to loop through the input array:

<LayerCake
  padding={{ top: 2, right: 6, bottom: 2, left: 6 }}
  x={extentGetters.find(d => d.field === 'x').accessor}
  y={extentGetters.find(d => d.field === 'y').accessor}
  {data}
  xDomain={$xDomain}
  yDomain={$yDomain}
>
  <Svg>
    <Line
      stroke={'#000'}
    />
  </Svg>
</LayerCake>

Would become:

<LayerCake
  padding={{ top: 2, right: 6, bottom: 2, left: 6 }}
  x={extentGetters.x.accessor}
  y={extentGetters.x.accessor}
  {data}
  xDomain={$xDomain}
  yDomain={$yDomain}
>
  <Svg>
    <Line
      stroke={'#000'}
    />
  </Svg>
</LayerCake>

There was maybe a reason why I made the input an array, though, so I'll look into this. It would also be a breaking change.

Better type documentation for components

Currently, all of the components have jsdoc comments but the vs code extension doesn't seem to recognize them well.

The component description only gets pulled in (when hovering over the component's import statement) if the format is such where the @type definitions are included in one big comment at the top immediately above the exported variables like this:

<script>
  /**
    Generates an SVG marker containing a marker for a triangle makes a nice arrowhead. Add it to the named slot called "defs" on the SVG layout component.
    @type {String} [fill='#000'] โ€“ The arrowhead's fill color.
    @type {String} [stroke='#000'] โ€“ The arrowhead's fill color.
  */
  export let fill = '#000';
  export let stroke = '#f0c';
</script>

<marker id="arrowhead" viewBox="-10 -10 20 20" markerWidth="17" markerHeight="17" orient="auto">
  <path d="M-6,-6 L 0,0 L -6,6" fill="{fill}" stroke="{stroke}"/>
</marker>

This yields a popup when imported like this, which is helpful because it has the comment description. It has undefined for the props though:

Screen Shot 2021-12-21 at 12 16 42 AM

In that format the popups over each individual property are also undefined and it pulls in that entire first comment, which isn't great:

Screen Shot 2021-12-21 at 12 17 17 AM

When the component is formatted like this:

<script>
  /**
    Generates an SVG marker containing a marker for a triangle makes a nice arrowhead. Add it to the named slot called "defs" on the SVG layout component.
  */
  /** @type {String} [fill='#000'] โ€“ The arrowhead's fill color. */
    export let fill = '#000';
  /** @type {String} [stroke='#000'] โ€“ The arrowhead's fill color. */
  export let stroke = '#f0c';
</script>

<marker id="arrowhead" viewBox="-10 -10 20 20" markerWidth="17" markerHeight="17" orient="auto">
  <path d="M-6,-6 L 0,0 L -6,6" fill="{fill}" stroke="{stroke}"/>
</marker>

The top-level hover now loses the main comment, but the individual props are nicely annotated:
Screen Shot 2021-12-21 at 12 18 25 AM
Screen Shot 2021-12-21 at 12 18 33 AM

But it still shows a lot of undefined fields.

Any thoughts?

sveltekit adapter static cannot build, npm run dev works fine

Hi,

the error I got is

> Using @sveltejs/adapter-static
TypeError: Cannot destructure property 'width' of 'getContext(...)' as it is undefined.
    at file:///D:/sveltekit%20projects/capacitor/.svelte-kit/output/server/entries/pages/layercake/AxisX.svelte
...

I rename the component folder to layercake and put it inside route, npm run dev works fine, I thought the problem is getContext but I tried using other component that use getContext and build normally, any tips ?

Thanks

Piechart

Help me creating pie chart in layercake

provide write access to data with user-defined setter function

im trying to make an editable chart, where i can click and drag data points
use case: interactive linear interpolation, similar to smooth.js and jsplinter
could be generalized to a svelte svg editor : )

i noticed that the accessor functions are made only for read access

we could extend the existing design like

function accessor(dataPoint, newValue = undefined) {
  if (newValue == undefined) {
    // get
    return dataPoint.x;
  }
  // set
  dataPoint.x = newValue;
  return true; // success
}

but for better performance, we should have separate getter and setter functions (unswitching)

the api could look like

<LayerCake
  x={p => p.myX}
  xSetter={(p, v) => (p.myX = v)}
>

in the trivial case <LayerCake x={'myX'}>, layercake can auto-generate a setter function

here is my modified Scatter.svg.svelte

<script>
  import { getContext } from 'svelte';

  const { data, xGet, yGet, xScale, yScale } = getContext('LayerCake');

  export let r = 5;
  export let fill = '#000';
  export let stroke = '#0cf';
  export let strokeWidth = 0;
  export let dx = 0;
  export let dy = 0;

  function handleMouseDown(startEvent, dataIndex) {
    const moveMouse = (moveEvent) => {
      // https://github.com/d3/d3-scale#continuous_invert
      $data[dataIndex] = {
        // TODO fix offset. why 25 x 10?
        myX: $xScale.invert(moveEvent.offsetX - 25),
        myY: $yScale.invert(moveEvent.offsetY - 10)
      };
    };
    const stopMove = (stopEvent) => {
      console.log('stop move');
      document.removeEventListener('mousemove', moveMouse);
      document.removeEventListener('mouseup', stopMove);
      //document.removeEventListener('mouseleave', stopMove); // TODO
    };
    document.addEventListener('mouseup', stopMove);
    //document.addEventListener('mouseleave', stopMove); // TODO

    // start moving
    document.addEventListener('mousemove', moveMouse);
  }
</script>

<g class="scatter-group">
  {#each $data as d, dIdx}
    <circle
      cx={$xGet(d) + (typeof dx === 'function' ? dx($xScale) : dx)}
      cy={$yGet(d) + (typeof dy === 'function' ? dy($yScale) : dy)}
      {r}
      {fill}
      {stroke}
      stroke-width={strokeWidth}
      on:mousedown={(event) => handleMouseDown(event, dIdx)}
    />
  {/each}
</g>

<style>
  .scatter-group circle:hover {
    fill: red !important;
  }
</style>

Ever expanding SVGs on deployed site

I'm trying to debug this but can't seem to figure it out.

I place my visualizations in several containers, using tailwind. There's a max-width in pixels on the main container, and I'm using 100% w/h for my chart containers.

Developing locally seems fine, but if I run npm run build and deploy my visualizations are expanding, forever.

growingsvg

It's adding +1 to the svg height dynamically. I'm getting the 'Target div has a negative width' error in the console, but only in Safari.

Sorry about being vague, but I'm a bit stuck :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.