Skip to content

Commit

Permalink
Merge branch 'main' into 107-make-sure-vue-portal-destinations-are-un…
Browse files Browse the repository at this point in the history
…iquely-scoped-to-the-component
  • Loading branch information
Lucas1981 authored Nov 29, 2022
2 parents d7c58a9 + 0d97e2f commit a0062fc
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 74 deletions.
26 changes: 11 additions & 15 deletions src/components/core/lume-line/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,22 @@ To show a point, provide `xScale`, `yScale`, `ìndex`, `values`, `color`, `dashe

```html
<lume-line
:path-definition="pathDefinition"
:x-scale="xScale"
:y-scale="yScale"
:values="values"
:index="index"
:color="color"
:dashed="isDashed(index)"
:transition="transition"
/>
```

## API

### Props

| Name | Type | Default | Description |
| ------------ | ----------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `xScale` | `ScaleGenerator or ScaleLinear<number, number>` | Required | A d3 scale or a scale generator function to override the default Y scale. |
| `yScale` | `ScaleGenerator or ScaleLinear<number, number>` | Required | A d3 scale or a scale generator function to override the default Y scale. |
| `values` | `Array<number>` | Required | The values with which to determine the location on the value scale. Values should be relative to index -1 and index respectively. |
| `index` | `number` | Required | The index with which to determine the location based on the label scale. |
| `color` | `string` | `01` | Indicating what color to use, representing an index to one of the available Lume colors. |
| `dashed` | `boolean` | `false` | Indicating whether or not this line should be displayed as dashed or solid. |
| `transition` | `boolean` | `true` | Indicating whether or not this line should animate. |
| Name | Type | Default | Description |
| ---------------- | ----------------------------------------------- | -------- | -------------------------------------------------------------------------------------------- |
| `pathDefinition` | `Computed<string>` | Required | A string representing a path that can be fed into the `d` attribute of an SVG `path`element. |
| `xScale` | `ScaleGenerator or ScaleLinear<number, number>` | Required | A d3 scale or a scale generator function to override the default Y scale. |
| `color` | `string` | `'01'` | Indicating what color to use, representing an index to one of the available Lume colors. |
| `width` | `number` | `2` | Indicating the stroke width of the line. |
| `dashed` | `boolean` | `false` | Indicating whether or not this line should be displayed as dashed or solid. |
| `transition` | `boolean` | `true` | Indicating whether or not this line should animate. |

NOTE: A composable `useLineValues` exists to convert indices and values along with their scales to path definitions. For an example of how to use this, see `LumeLineGroup`.
11 changes: 10 additions & 1 deletion src/components/core/lume-line/lume-line.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@ import { mount } from '@vue/test-utils';

import LumeLine from './lume-line.vue';

import { getLinePathDefinition } from '@/composables/line-values';

import { data, xScale, yScale } from '@test/unit/mock-data';

const propsData = { index: 0, values: data[0].values, xScale, yScale };
const pathDefinition = getLinePathDefinition(
1,
[data[0].values[0].value, data[0].values[1].value],
xScale,
yScale
);

const propsData = { pathDefinition: pathDefinition.value, xScale };

describe('chart-container.vue', () => {
test('mounts component', () => {
Expand Down
42 changes: 7 additions & 35 deletions src/components/core/lume-line/lume-line.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@

<script lang="ts">
import { computed, defineComponent, onMounted, PropType, ref } from 'vue';
import { line, ScaleBand, ScaleLinear } from 'd3';
import { Scale } from '@/composables/scales';
import { Colors } from '@/constants';
import { getDomainLength, getScaleStep, isBandScale } from '@/utils/helpers';
import { getDomainLength } from '@/utils/helpers';
import { svgCheck } from '@/utils/svg-check';
const LUME_TRANSITION_TIME_FULL = 1; // 1s
Expand All @@ -33,6 +34,10 @@ const DEFAULT_LINE_WIDTH = 2; // 2px
export default defineComponent({
props: {
pathDefinition: {
type: String,
required: true,
},
color: {
type: String,
default: Colors.Skyblue,
Expand All @@ -45,10 +50,6 @@ export default defineComponent({
type: Number,
default: DEFAULT_LINE_WIDTH,
},
values: {
type: Array as PropType<Array<number>>,
required: true,
},
dashed: {
type: Boolean,
default: false,
Expand All @@ -57,10 +58,6 @@ export default defineComponent({
type: Function as PropType<Scale>,
required: true,
},
yScale: {
type: Function as PropType<Scale>,
required: true,
},
transition: {
type: Boolean,
default: true,
Expand All @@ -80,36 +77,11 @@ export default defineComponent({
return props.transition && (props.index - 1) * animationDuration.value;
});
const xAxisOffset = computed(() => getScaleStep(props.xScale) / 2);
function findLinearX(_: unknown, index: number) {
return (props.xScale as ScaleLinear<number, number>)(
props.index + (index - 1)
);
}
function findBandX(_: unknown, index: number) {
return (
(props.xScale as ScaleBand<string | number>)(
props.xScale.domain()[props.index + (index - 1)]
) + xAxisOffset.value
);
}
const pathDefinition = computed(() => {
const lineFn = line<number>()
.x(isBandScale(props.xScale) ? findBandX : findLinearX)
.y((d) => props.yScale(d));
return lineFn(props.values);
});
onMounted(() => svgCheck(root.value));
return {
animationDelay,
animationDuration,
pathDefinition,
root,
};
},
Expand Down
52 changes: 29 additions & 23 deletions src/components/groups/lume-line-group/lume-line-group.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,21 @@

<!-- Line groups -->
<g
v-for="dataset in computedGroupData"
v-for="(dataset, datasetIndex) in computedGroupData"
:key="dataset.label"
class="lume-line-group__group"
>
<g class="lume-line-group__lines">
<lume-line
v-for="(_, index) in dataset.values"
:key="`line-${index}`"
:path-definition="getPathDefinition(index, datasetIndex)"
:color="dataset.color"
:dashed="dataset.isDashed(index)"
:index="index"
:transition="transition"
:values="getLineValues(index, dataset.values)"
:width="options.lineWidth"
:x-scale="xScale"
:y-scale="yScale"
/>
</g>
<g
Expand All @@ -42,7 +41,7 @@
:color="dataset.color"
:index="index"
:radius="pointRadius"
:value="getPointValue(index, dataset.values)"
:value="getPointValue(index, datasetIndex)"
:x-scale="xScale"
:y-scale="yScale"
/>
Expand All @@ -53,17 +52,18 @@

<script lang="ts">
import { computed, defineComponent, toRefs } from 'vue';
import { ScaleLinear } from 'd3';
import LumeLine from '@/components/core/lume-line';
import LumePoint from '@/components/core/lume-point';
import { getXByIndex } from '@/composables/scales';
import { useLineNullValues } from '@/composables/line-null-values';
import { getLinePathDefinition } from '@/composables/line-values';
import { withGroupProps } from '@/components/groups/composables/group-props';
import { LineChartOptions } from '@/composables/options';
import { getHighestValue } from '@/utils/helpers';
import { DatasetValueObject } from '@/types/dataset';
export default defineComponent({
components: { LumeLine, LumePoint },
Expand Down Expand Up @@ -97,7 +97,6 @@ export default defineComponent({
if (props.hoveredIndex === -1) return;
const highestValue = getHighestValue(data.value, props.hoveredIndex);
const x = getXByIndex(xScale.value, props.hoveredIndex);
return {
Expand All @@ -110,24 +109,31 @@ export default defineComponent({
() => options.value?.lineWidth * 2 || undefined // If no `lineWidth`, returns NaN which needs to be undefined
);
function getLineValues(
index: number,
computedLineValues: Array<DatasetValueObject<number>>
) {
// First value
if (index === 0) return [];
return [
computedLineValues[index - 1]?.value,
computedLineValues[index]?.value,
];
function getValuesFromDataset(datasetIndex: number) {
return computedGroupData.value[datasetIndex].values;
}
function getPathDefinition(lineIndex: number, datasetIndex: number) {
const values =
lineIndex === 0
? []
: [
getValuesFromDataset(datasetIndex)[lineIndex - 1]?.value,
getValuesFromDataset(datasetIndex)[lineIndex]?.value,
];
return (
getLinePathDefinition(
lineIndex,
values,
xScale.value,
yScale.value as ScaleLinear<number, number>
) || ''
);
}
function getPointValue(
index: number,
computedLineValues: Array<DatasetValueObject<number>>
) {
return computedLineValues[index]?.value;
function getPointValue(pointIndex: number, datasetIndex: number) {
return getValuesFromDataset(datasetIndex)[pointIndex]?.value;
}
function isPointActive(index: number) {
Expand All @@ -136,7 +142,7 @@ export default defineComponent({
return {
computedGroupData,
getLineValues,
getPathDefinition,
getPointValue,
isPointActive,
overlayLineAttributes,
Expand Down
20 changes: 20 additions & 0 deletions src/composables/line-values.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getLinePathDefinition } from './line-values';

import { data, xScale, yScale } from '@test/unit/mock-data';

describe('line-values', () => {
it('should generate a path definition string', () => {
const pathDefinition = getLinePathDefinition(
1,
[data[0].values[0].value, data[0].values[1].value],
xScale,
yScale
);

// should return a string
expect(typeof pathDefinition).toBe('string');

// shouldn't have any NaNs - otherwise something would be wrong
expect(pathDefinition?.includes('NaN')).toBeFalsy();
});
});
55 changes: 55 additions & 0 deletions src/composables/line-values.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { line, ScaleBand, ScaleLinear } from 'd3';

import { getScaleStep, isBandScale } from '@/utils/helpers';
import { Scale } from './scales';

/**
* Returns the X acessor function for a `ScaleBand`.
*
* @param scale The chart X axis scale.
* @param lineIndex The index of the data point of the line to render.
* @returns The X acessor function to provide to d3 `line` method.
*/
function getFindBandX(scale: ScaleBand<string | number>, lineIndex: number) {
const xAxisOffset = getScaleStep(scale) / 2;
return (_: unknown, index: number) =>
scale(scale.domain()[lineIndex + (index - 1)]) + xAxisOffset;
}

/**
* Returns the X acessor function for a `ScaleLinear`.
*
* @param scale The chart X axis scale.
* @param lineIndex The index of the data point of the line to render.
* @returns The X acessor function to provide to d3 `line` method.
*/
function getFindLinearX(scale: ScaleLinear<number, number>, lineIndex: number) {
return (_: unknown, index: number) => scale(lineIndex + (index - 1));
}

/**
* Generates the path definition between the point at a provided index (`lineIndex`)
* and the previous point relative to that index.
*
* @param lineIndex The index of the data point of the line to render.
* @param values An array of the previous and current data value
* @param xScale The chart X axis scale.
* @param yScale The chart Y axis scale.
* @returns A path definition string to provide to the `d` attribute of `<path>`.
*/
export function getLinePathDefinition(
lineIndex: number,
values: Array<number>,
xScale: Scale,
yScale: ScaleLinear<number, number>
) {
const lineFn = line<number>()
.x(
isBandScale(xScale)
? getFindBandX(xScale, lineIndex)
: getFindLinearX(xScale, lineIndex)
)
.y((d) => yScale(d));

return lineFn(values);
}

0 comments on commit a0062fc

Please sign in to comment.