Giter VIP home page Giter VIP logo

flutter-charts's Introduction

Charts painter pub package

Idea behind this lib is to allow highly customizable charts. By having items and decorations as Widgets or predefined renderers where you can achieve the look of desired chart.

Read the migration guide if you are interested in migrating from 2.0 to 3.0.

Check out web demo to see what’s possible to do with the charts_painter

Usage

Add it to your package’s pubspec.yaml file

charts_painter: latest

Start with the Chart

The Widget you can use is Chart or AnimatedChart if you want to show animations when changing chart state. It has parameters of height, width and ChartState.

Chart State

ChartState describes how the chart should look. The chart drawing is divided into sections, so the ChartState has these parameters:

  • Data: What is the data that needs to be shown
  • Item options: How to draw that data points into items on chart
  • Decorations: Various additional objects that enhance and completes look of the chart. They are divided into backgroundDecorations (behind items) or in foregroundDecorations (in front of items).
  • Chart behaviour: Not used for drawing, but contain scrollable and item click logic

Now we will explain each of these:

Data

The ChartData only required parameter is List<List<ChartItem>>. So, most basic data would look like:

ChartData([
    [
      ChartItem(2),
      ChartItem(5),
      ChartItem(7),
      ChartItem(11),
      ChartItem(4),
    ]
  ])

The reason for double list (List<List<) is that you can display multiple data lines alongside each other.

ChartData([
    [2, 6, 8, 4, 6, 8].map((e) => ChartItem<void>(e.toDouble())).toList(),
    [3, 5, 2, 7, 0, 4].map((e) => ChartItem<void>(e.toDouble())).toList(),
  ],);

Chart Item requires max height parameter, but also has optional min and T value which can be any kind of value that you can attach to your items, if you have need for it.

When displaying multiple data lines, you might be interested in dataStrategy parameter. It controls how these multiple lines are drawn. For example, if you want to stack bars, one on top of another, you can use StackDataStrategy.

Parameter valueAxisMaxOver will add that value to currently the highest value that functions like a some sort of top padding.

Item options

For item options, you can use one of three defined options:

  • BarItemOptions - for drawing bar or candle items
  • BubbleItemOptions - for draw bubble items
  • WidgetItemOptions - for drawing any kind of widget.

You could create anything with WidgetItemOptions, but Bar and Bubble are often used and here they are drawn directly on canvas to make sure chart is performant. This graphic might help when choosing:

Options have several parameter, and the required is itemBuilder. With it, you describe how to build an item. For example, to make bar item:

barItemBuilder: (data) {
    return BarItem(
      radius: const BorderRadius.vertical(
        top: Radius.circular(24.0),
      ),
      color: Colors.red.withOpacity(0.4),
    );
  },

The data that’s passed into the builder can be used to build different kind of item based on the item value (data.item.value), his index in data (data.itemIndex) or based on which data list it belongs to (data.listIndex).

Besides builder, the other useful parameters in item options are maxBarWidth , minBarWidth , startPosition , padding.

If you want to listen to item taps you can do it by setting ChartBehaviour(onItemClicked) - you can read more about ChartBehaviour below. In case of a WidgetItemOptions, you could also provide GestureDetectors and Buttons and they will all work.

Decorations

Decorations enhance and complete the look of the chart. Everything that’s drawn on a chart, and it’s not a chart item is considered a decoration. So that means a lot of the chart will be a decoration. Just like with the items, you can use WidgetDecoration to draw any kind of the decoration, but the most common cases for decoration are already made on a canvas and ready to be used:

horizontal_decoration Horizontal decoration vertical_decoration Vertical decoration grid_decoration Grid decoration
sparkline_decoration Sparkline decoration

Widget decoration

There are only two parameters in WidgetDecoration:

WidgetDecoration(
    widgetDecorationBuilder: (context, chartState, itemWidth, verticalMultiplayer) {
      return Container(); // Your widget goes here
    },
    margin: const EdgeInsets.only(left: 20),
)

The builder returns context, chartState where from you can read details like all the values. And itemWidth and verticalMultiplier can help with laying out and position the decoration:

If you do add margins to the chart, your decoration widget will be positioned from start of the chart (not affected by the margins), so you can draw in the margins. You can add padding that equals the chart margins which will set you to the start of the first item so calculations including itemWidth or verticalMultiplier works correctly:

    widgetDecorationBuilder: (context, chartState, itemWidth, verticalMultiplayer) {
      return Padding(padding: chartState.defaultMargin, child: YourWidget());
    },

Chart behaviour

Chart behaviour has just two parameters:

  • scrollSettings - used to config the chart to be scrollable or not. You still need to wrap it with SingleChildScrollView.
  • onItemClicked - when set the tap events on items are registered and will invoke this method. If you're using WidgetItemOptions, you could set a gesture detector there, but this works with both BarItemOptions, BubbleItemOptions and WidgetItemOptions.

Complete example

So, to wrap all of this up. The most minimal example of a bar chart with data, barItemOptions* and no decorations would looks like:

Chart(
    state: ChartState<void>(
      data: ChartData(
          [[3,5,7,9,4,3,6].map((e) => ChartItem<void>(e.toDouble())).toList()]
      ),
      itemOptions: BarItemOptions()
    ),
  );

Which will produce a chart looking like:

A bit more complex example with two data lists coloured differently and grid decoration would look like:

Chart(
    state: ChartState<void>(
        data: ChartData(
          [
            [3, 5, 7, 9, 4, 3, 6].map((e) => ChartItem<void>(e.toDouble())).toList(),
            [5, 2, 8, 4, 5, 5, 2].map((e) => ChartItem<void>(e.toDouble())).toList(),
          ],
        ),
        itemOptions: BarItemOptions(barItemBuilder: (itemBuilderData) {
          // Setting the different color based if the item is from first or second list
          return BarItem(color: itemBuilderData.listIndex == 0 ? Colors.red : Colors.blue);
        }),
        backgroundDecorations: [
          HorizontalDecoration(axisStep: 2, showValues: true),
        ])
  );

Which produces a chart:

There’s a lot more things possible with this package, but to keep this README readable, we recommend you checking out the demo and showcase web app.

Scrollable chart

Charts can also be scrollable, to use scroll first you have to wrap chart your chart in SingleChildScrollView widget. Then in ChartBehaviour make sure you set isScrollable to true.

https://raw.githubusercontent.com/infinum/flutter-charts/master/assets/scrollable_chart.gif

scrollable_chart

To make sure you can make any chart you want, we have included DecorationsRenderer as widget that you can use outside of the chart bounds. That is useful for fixed legends:

More examples

Line charts

Line chart with multiple values

example code

https://raw.githubusercontent.com/infinum/flutter-charts/master/assets/line_chart_animating.gif

Bar charts

Bar chart with area

example code

https://raw.githubusercontent.com/infinum/flutter-charts/master/assets/bar_chart_animating.gif

flutter-charts's People

Contributors

doms99 avatar itsjokr avatar lukaknezic avatar michalsrutek avatar nilsreichardt 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

flutter-charts's Issues

Animated Chart Type Error when using duration over 30 milliseconds

Description:

I encountered an issue with the Animated Chart in your charts_painter library. Specifically, when I set the animation duration over 30 milliseconds, I receive the following error:

Exception: type 'ChartItem<void>' is not a subtype of type 'ChartItem<MyCustomModel?>' of 'startValue'
or
Exception: type 'ChartItem<void>' is not a subtype of type 'ChartItem<MyCustomModel?>' of 'endValue'

Stacktrace

String: type 'ChartItem<void>' is not a subtype of type 'ChartItem<MyCustomModel?>' of 'endValue'
#0      ChartItem.animateTo (package:charts_painter/chart/model/geometry/chart_item.dart:21:41)
#1      ChartItemsLerp._lerpItemList.<anonymous closure> (package:charts_painter/chart/model/data/chart_data.dart:237:25)
#2      new _GrowableList.generate (dart:core-patch/growable_array.dart:136:28)
#3      ChartItemsLerp._lerpItemList (package:charts_painter/chart/model/data/chart_data.dart:193:12)
#4      ChartItemsLerp.lerpValues.<anonymous closure> (package:charts_painter/chart/model/data/chart_data.dart:181:14)
#5      new _GrowableList.generate (dart:core-patch/growable_array.dart:136:28)
#6      ChartItemsLerp.lerpValues (package:charts_painter/chart/model/data/chart_data.dart:180:12)
#7      ChartData.lerp (package:charts_painter/chart/mode<…>

However, the error doesn't occur when using the non-animated Chart or when the animation duration is set to or under 30 milliseconds.

Steps to Reproduce:

  1. Use the Animated Chart
  2. Set the animation duration to 300 milliseconds.
  3. Observe the error.

Expected Behavior:

The chart should animate without errors.

Actual Behavior:

An error is thrown when the animation duration is 300 milliseconds.

Workaround:

Use the Chart without animation.
Set the animation duration to 30 milliseconds.

Environment:

Flutter version: 3.13.2
charts_painter version: 3.1.1
OS: macOS with an iOS Simulator iPhone 14 Pro Max

Please add exactly explanation for scrolling to your docs

I want to use a line chart with scrolling but didnt find any explanation or sample code for it.
I added SingleChildScrollView but there is no isScrollable option inside of behaviour.
Furthermore, it is not clear to me where the maximum X Axis should be added so that scrolling can work.
I always see everything in one view. visibleItems is not working as well.

 body: Center(
            child: Expanded(
                child: SingleChildScrollView(
                    controller: _scrollController,
                    scrollDirection: Axis.horizontal,
                    child: SizedBox(
                        width: 300,
                        height: 300,
                        child: Chart(
                          state: ChartState<void>(
                            data: ChartData.fromList(
                                [2, 3, 4, 4, 7, 6, 2, 5, 4]
                                    .map((e) => ChartItem<void>(e.toDouble()))
                                    .toList(),
                                axisMax: 10,
                                axisMin: 1),
                            itemOptions: BarItemOptions(
                              padding:
                                  const EdgeInsets.symmetric(horizontal: 2.0),
                              barItemBuilder: (_) => BarItem(
                                color: Theme.of(context).colorScheme.secondary,
                                radius: BorderRadius.vertical(
                                    top: Radius.circular(12.0)),
                              ),
                            ),
                            behaviour: ChartBehaviour(
                                scrollSettings:
                                    const ScrollSettings(visibleItems: 3),
                                onItemClicked: (value) => print("clicked")),

                            // itemOptions: WidgetItemOptions(
                            //     widgetItemBuilder: (_) => Container()),
                            backgroundDecorations: [
                              HorizontalAxisDecoration(
                                axisStep: 2,
                                showValues: true,
                                lineColor: Colors.green,
                              ),
                              GridDecoration(
                                showVerticalValues: true,
                                showHorizontalValues: true,
                                verticalAxisStep: 1,
                                horizontalAxisStep: 1,
                                gridColor: Colors.grey,
                                textStyle: const TextStyle(
                                  color: Colors.black,
                                  fontSize: 14,
                                ),
                              ),
                              // SparkLineDecoration(
                              //   smoothPoints: true,
                              //   lineColor: FitnessAppTheme.accentColor,
                              // ),
                            ],
                            foregroundDecorations: [
                              SparkLineDecoration(
                                lineWidth: 2.0,
                                // gradient: lineColor(minY, maxY),
                                smoothPoints: true,
                              ),
                            ],
                          ),
                        )))))

ValueDecoration Alignment not working with maxBarWidth

Screenshot 2022-03-23 at 09 04 05

When setting maxBarWidth in the item options the ValueDecoration alignment is not properly aligned anymore.

...
                foregroundDecorations: [
                  ValueDecoration(
                    textStyle: Theme.of(context).textTheme.bodyText2,
                    alignment: Alignment.topCenter,
                  ),
                ],
                itemOptions: BarItemOptions(
                  maxBarWidth: 100,
...

Vertical Axis Values are clipped in scrollable chart

I have a scrollable chart and want to show vertical axis values from index. However these are clipped as the size allowed for the value is too small for the value from index - unless I make the width of the chart really wide. The values in the chart image below should be 12am, 5am, 10am etc.

Screen Shot 2022-09-23 at 4 45 48 pm

If I make the text smaller, it shows more of the values.

Screen Shot 2022-09-23 at 5 25 55 pm

Or if I make the chart width really wide, then I can get more of the values showing.

Screen Shot 2022-09-23 at 5 35 29 pm

But how can I get the values to show without making the chart really wide? If the verticalAxisStep is set, is it possible to allow the values to show past the allowed space without clipping them? (Hope that makes sense)

The code for this chart is:

Expanded(
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: SizedBox(
              width: 160 * SizeConfig.safeBlockHorizontal,
              height: widget.chartHeight,
              child: 
                  Chart(
                state: _chartState,
              ),
            ),
          ),
        ),
_chartState = ChartState.line(
      ChartData.fromList(
        temperatures.map((e) => BubbleValue<void>(e.toDouble())).toList(),
        axisMax: maxY,
        axisMin: minY,
      ),
      behaviour: const ChartBehaviour(
        isScrollable: true,
      ),
      backgroundDecorations: [
        GridDecoration(
          showVerticalValues: true,
          showHorizontalValues: false,
          verticalAxisStep: 5,
          gridColor: CustomColors.darkBlue.withOpacity(0),
          textStyle: const TextStyle(
            color: CustomColors.darkBlue,
            fontSize: 14,
          ),
          verticalAxisValueFromIndex: (index) {
            var now = DateTime.now();
            var midnight = DateTime(now.year, now.month, now.day);
            var time = DateFormat('h').format(midnight.add(
                  Duration(minutes: index.toInt() * 60),
                )) +
                DateFormat('a')
                    .format(midnight.add(
                      Duration(minutes: index.toInt() * 60),
                    ))
                    .toLowerCase();
            return time;
          },
        ),
      ],
      foregroundDecorations: [
        SparkLineDecoration(
          lineWidth: 2.0,
          gradient: lineColor(minY, maxY),
          smoothPoints: true,
        ),
      ],
    );

Incorrect item width for OnItemClicked

The width of the element to detect clicks on chart elements is calculated by the size of the entire chart widget. But when we use Y-axis decoration with showValues: true, the width of the chart decreases. Thus, the width of the element (bar) decreased. This causes a problem in _ChartWidget. _getClickLocation due to an incorrectly calculated item index that is forwards to: ChartBehavior._onChartItemClicked

Issue with horizontal axis values

Hi, I would basically like a scrollable chart like the one seen in the example gif:

However, I'm struggling with the values on the horizontal axis. They seem to disappear, as soon as the chart is scrollable (width > screen width). This is also the case for the scrollable chart in the examples:

1
2

Am I missing something here?

Flutter 3.0.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision fb57da5f94 (9 months ago) • 2022-05-19 15:50:29 -0700
Engine • revision caaafc5604
Tools • Dart 2.17.1 • DevTools 2.12.2

Thanks!

Is x-axis fixed step only?

Just to clarify, looking at the examples, it seems positions on x-axis are not based on values, but on indices. So, it is not possible to draw line chart where x-axis values have variable intervals?

For example a time series line chart where point 1 & point 2 are 5 minutes apart, but point 2 & point 3 are 20 minutes apart.

Is it possible to have two lines with different horizontal axis values?

I would like to create a line chart with two lines. But the horizontal axis values for each line need to be different. One is a temperature (so could be 0 to 40 degrees), the other one is a chance of rain (so could be 0% to 100%). Is this possible?

Hopefully the question makes sense.

Not a bug, just failing to understand

Hi,
I really like the solution and it's simplicity.
that being said, I can't seem to understand from the documentation how to achieve the things you display in your gorgeous demo.
mainly I am interested in two things:

  1. control over vertical and horizontal descriptions.
    E.G a chart of some values divided by days and underneath a Sun,Mon,Tue etc
  2. how do you produce the popups with little values upon pressing a bar?

Different types of area graphs

Hi
I am planning to create area graphs using this library like Step-Area Charts, Spline-Area Charts, Streamgraphs, and Smoothed-Line Charts.

Unfortunately, I've been struggling to find a straightforward method to create these graphs, and I'm also facing challenges with labeling the chart and implementing click-related animations. If anyone has any suggestions or solutions to these issues, I would greatly appreciate your input. Thank you in advance!

The named parameter 'chartBehaviour' isn't defined

My chart is working well, but I'd like to add onItemClicked. But when I try to add the chartBehaviour parameter, it gives me the error saying it isn't defined.

I am using the feature/render-object branch, and here is my code

Widget rangeChart(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Container(
        width: 100.0,
        child: Chart(
          state: ChartState.bar(
            ChartData.fromList(
                forecastTemps.map((CandleItem item) => CandleValue<void>(item.min, item.max)).toList(),
                axisMax: dailyHigh.toDouble(),
                axisMin: dailyMin.toDouble()
            ),
            
            itemOptions: BarItemOptions(
              padding: EdgeInsets.symmetric(horizontal: candlePadding),
              radius: BorderRadius.all(Radius.circular(18.0)),
              gradient: candleColor(),
            ),
            chartBehaviour: ChartBehaviour(onItemClicked: (item) {
              
            }),
            backgroundDecorations: [
              GridDecoration(
                showHorizontalValues: false,
                showVerticalValues: true,
                verticalAxisValueFromIndex: (int value) {
                  return DateFormat.E()
                      .format(now.add(new Duration(days: value+1)))
                      .toString();
                },
                verticalAxisStep: 1,
                horizontalAxisStep: 3,
                textStyle: TextStyle(color: Colors.white),
                gridColor: Theme.of(context).dividerColor,
              ),
            ],
            foregroundDecorations: [
              ValueDecoration(
                //alignment: Alignment.topCenter,
                textStyle: TextStyle(color: Colors.white),
              ),
              ValueDecoration(
                textStyle: TextStyle(color: Colors.white),
                alignment: Alignment.bottomCenter,
                valueGenerator: (item) => item.min ?? 0,
              ),
            ],
          ),
        ),
      ),
    );
  }

Couldn't implement the onClick action.

Hello,

I liked this package its easy to implement and has very good visuals. My problem is simple I guess but I couldn't understand the reason behind.

So I created a chart and wanted to just show the value of the Y axis when clicked, however, when I add the solution that I found in the issues section, my code was unable to detect the clicking action. I would be very grateful if you could point the problem's reason.

Here is my code;

`
import 'dart:math';
import 'package:charts_painter/chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

class LineChartScreen extends StatefulWidget {
LineChartScreen({Key? key}) : super(key: key);

@OverRide
_LineChartScreenState createState() => _LineChartScreenState();
}

class _LineChartScreenState extends State {
final Map<int, List> _values = <int, List>{};
double targetMax = 0;
final bool _showValues = true;
final bool _smoothPoints = true;
final bool _fillLine = true;
final bool _showLine = true;
final bool _stack = false;
int minItems = 15;
List<List> listo1 = [
[0, 0, 0, 1, 5, 5, 6, 4, 5, 6, 4, 1, 0, 0, 0],
[6, 4, 8, 9, 5, 4, 2, 2, 7, 5, 6, 1, 9, 6, 3],
];
int? currentItem = 0;

@OverRide
void initState() {
super.initState();
initializeList();
}

void initializeList() {
_values.addAll(List.generate(2, (index) {
List<ChartItem> _items = [];
for (int i = 0; i < minItems; i++) {
_items.add(ChartItem(listo1[index][i]));
}
return _items;
}).asMap());
}

List<List<ChartItem>> _getMap() {
return [
_values[0]!
.asMap()
.map<int, ChartItem>((index, e) {
return MapEntry(index, e);
})
.values
.toList(),
_values[1]!
.asMap()
.map<int, ChartItem>((index, e) {
return MapEntry(index, e);
})
.values
.toList(),
];
}

@OverRide
Widget build(BuildContext context) {
return Column(
children: [
Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Stack(
children: [
LineChart.multiple(
_getMap(),
stack: _stack,
height: MediaQuery.of(context).size.height * 0.4,
itemColor: Colors.green.withOpacity(_showLine ? 1.0 : 0.0),
lineWidth: 2.0,
chartItemOptions: BubbleItemOptions(
maxBarWidth: _showLine ? 0.0 : 6.0,
bubbleItemBuilder: (data) {
final isCurrent = data.itemIndex == currentItem;
return BubbleItem(
color: [
Colors.red.withOpacity(isCurrent ? 1 : 0.5),
Colors.green.withOpacity(isCurrent ? 1 : 0.5),
Colors.blue.withOpacity(isCurrent ? 1 : 0.5),
][data.listIndex]);
},
),
chartBehaviour: ChartBehaviour(
onItemClicked: (item) {
print("something");
print(item.item.value);
setState(() {
currentItem = item.itemIndex;

                  });
                },
                onItemHoverEnter: (item) {
                  print(item.item.value);

                  setState(() {
                    currentItem = item.itemIndex;
                    print(currentItem);
                  });
                },
              ),
              smoothCurves: _smoothPoints,
              backgroundDecorations: [
                GridDecoration(
                  horizontalAxisUnit: "kWh",
                  showVerticalGrid: false,
                  showTopHorizontalValue: _showValues,
                  showVerticalValues: _showValues,
                  showHorizontalValues: _showValues,
                  horizontalAxisStep: _stack ? 3 : 1,
                  verticalAxisStep: 2,
                  textStyle: Theme.of(context).textTheme.caption,
                  gridColor: Theme.of(context)
                      .colorScheme
                      .primaryContainer
                      .withOpacity(0.2),
                ),
                SparkLineDecoration(
                  id: 'first_line_fill',
                  smoothPoints: _smoothPoints,
                  fill: true,
                  lineColor: Colors.green.withOpacity(_fillLine
                      ? _stack
                          ? 1.0
                          : 0.2
                      : 0.0),
                  listIndex: 0,
                ),
                SparkLineDecoration(
                  id: 'second_line_fill',
                  smoothPoints: _smoothPoints,
                  fill: true,
                  lineColor: Colors.red.withOpacity(_fillLine
                      ? _stack
                          ? 1.0
                          : 0.2
                      : 0.0),
                  listIndex: 1,
                ),
              ],
              foregroundDecorations: [
                SparkLineDecoration(
                  id: 'second_line',
                  lineWidth: 2.0,
                  smoothPoints: _smoothPoints,
                  lineColor: Colors.red.withOpacity(_showLine ? 1.0 : 0.0),
                  listIndex: 1,
                ),
                
              ],
            ),
          ],
        ),
      ),
    ),
  ],
);

}
}
`

Candle Charts

I am trying to create a candle chart with a specific range - for example 0 - 15. I cannot find an option to do this. Currently if I only have a value from 0 - 2 - then the highest value shown is 2. Is this functionality possible?

Fixed Axis on Scrollable Chart

The values on the horizontal axis for a fixed axis scrollable chart are cut off. Only 1 digit shows. This happens in the example app. How we I ensure that the horizontal axis shows the full value?
Screen Shot 2021-12-17 at 5 03 52 pm
p

Offset argument contained a NaN value. when ChartData.fromList receives a list with zeroes only.

Hi, many thanks for this awesome package. It's really simple and intuitive to use.

I'm getting a bug when rendering ChartState.bar graph. When the bar ChartData.fromList() contains all zeroes,
the error below is thrown. It renders okay, but there's a failing assertion.

Hope the example below is easily reproducable.

The relevant error-causing widget was
Chart<dynamic>
package:flutter/…/scheduler/binding.dart:862
(elided 6 frames from class _AssertionError, class _RawReceivePortImpl, class _Timer, and dart:async-patch)
The following RenderObject was being processed when the exception was fired: _RenderChartDecoration<Object>#d1591
RenderObject: _RenderChartDecoration<Object>#d1591
    parentData: offset=Offset(0.0, 0.0)
    constraints: BoxConstraints(0.0<=w<=174.5, 0.0<=h<=46.4)
    size: Size(174.5, 36.4)
════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by rendering library ═════════════════════════════════
Offset argument contained a NaN value.
'dart:ui/painting.dart':
Failed assertion: line 43 pos 10: '<optimized out>'

The relevant error-causing widget was
Chart<dynamic>
lib/page.dart:312

  @override
  Widget build(BuildContext context) {
    final byCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    //final byCount = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0];
    print(byCount);
    return chart.Chart(
      state: chart.ChartState.bar(
        chart.ChartData.fromList(
          byCount.map((e) => chart.BarValue(e.toDouble())).toList(),
        ),
        backgroundDecorations: [
          chart.GridDecoration(
            showHorizontalGrid: false,
            showVerticalGrid: false,
            showVerticalValues: true,
            verticalAxisValueFromIndex: (idx) => '${idx + 1}',
            gridWidth: 2,
            textStyle: Theme.of(context)
                .textTheme
                .subtitle2!
                .copyWith(fontSize: 8, fontWeight: FontWeight.bold),
            gridColor: Theme.of(context).dividerColor,
          ),
          chart.ValueDecoration(
            alignment: Alignment.topCenter,
            // valueGenerator: (_),
            textStyle:
                Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 8),
          ),
        ],
        // foregroundDecorations: [
        //   chart.HorizontalAxisDecoration(lineColor: Colors.brown),
        // ],
      ),
    );
}

Looking foward to your assistance. I tried digging into the code, but I got abit lost.

GridDecoration or VerticalAxisDecoration verticalValues truncated

Hi,

I try to use this package with a growing chart, which works and renders really well. It is really simple to implement.

But if I want to show the values of the vertical decoration (x-axis labels), the values will be truncated if the number indexes and the size of the values are growing.

Here is a code snippet based on the flutter example project.

import 'dart:async';

import 'package:charts_painter/chart.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Timer? _timer;
  final List<double> _dataPoints = [0];
  final bool _showBars = true;

  @override
  void initState() {
    super.initState();
    _timer =
        Timer.periodic(const Duration(milliseconds: 300), _updateDataPoints);
  }

  @override
  void dispose() {
    _timer!.cancel();
    super.dispose();
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _updateDataPoints(Timer timer) {
    _recordData();
  }

  void _recordData() {
    setState(() {
      _dataPoints.add(_counter.toDouble());
    });
  }

  double _calcVerticalSteps() {
    final division = (_dataPoints.length / 10);
    if (division < 1) return 1;
    if (division < 10) return 10;
    if (division < 100) return 100;
    return 1000;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            Container(
              padding: const EdgeInsets.only(top: 20),
              child: Chart(
                state: ChartState(
                  ChartData.fromList(
                    _dataPoints
                        .map((e) => BubbleValue<void>(e.toDouble()))
                        .toList(),
                  ),
                  itemOptions: const BubbleItemOptions(
                    padding: EdgeInsets.symmetric(horizontal: 2.0),
                    color: Colors.red,
                    maxBarWidth: 1.0,
                  ),
                  backgroundDecorations: [
                    GridDecoration(
                      showVerticalGrid: true,
                      verticalAxisStep: _calcVerticalSteps(),
                      showVerticalValues: true,
                      verticalTextAlign: TextAlign.center,
                      textStyle: const TextStyle(color: Colors.red),
                      horizontalAxisStep: 1,
                      gridColor: Theme.of(context).dividerColor,
                      showHorizontalValues: true,
                      verticalValuesPadding: EdgeInsets.zero,
                    ),
                    SparkLineDecoration(
                      lineWidth: 2.0,
                      lineColor: Theme.of(context).colorScheme.primary,
                    ),
                  ],
                  foregroundDecorations: [
                    ValueDecoration(
                      alignment: _showBars
                          ? Alignment.bottomCenter
                          : const Alignment(0.0, -1.0),
                      textStyle: Theme.of(context).textTheme.button!.copyWith(
                          color: (_showBars
                                  ? Theme.of(context).colorScheme.onPrimary
                                  : Theme.of(context).colorScheme.primary)
                              .withOpacity(0.0)),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Maybe someone could help here, how I could change my code, to show correct (not truncated values).

Incorrect item width for OnItemClicked

The width of the element to detect clicks on chart elements is calculated by the size of the entire chart widget. But when we use Y-axis decoration with showValues: true, the width of the chart decreases. Thus, the width of the element (bar) decreased. This causes a problem in _ChartWidget. _getClickLocation due to an incorrectly calculated item index that is forwards to: ChartBehavior._onChartItemClicked

TargetLineDecoration out of chart

Hello,

I'm using your widget to visualize stock data in my app. The line chart is showing the values from the past and I added a "TargetLineDecoration" to show the stop value.

It happens that the stock values over the time are much lower than the stop value. In this case the "TargetLineDecoration" is out of the chart (on top), somewhere on the screen. Please see the picture with the "TargetLineDecoration" in red.

Chart

charts.Chart(
                      height: 300,
                      state: charts.ChartState.line(
                        charts.ChartData.fromList(
                          result
                              .map((e) => charts.BubbleValue<void>(e))
                              .toList(),
                        ),
                        itemOptions: const charts.BubbleItemOptions(
                          padding: EdgeInsets.symmetric(horizontal: 2.0),
                          maxBarWidth: 4.0,
                        ),
                        foregroundDecorations: [
                          charts.TargetLineDecoration(
                              target: _stopKurs, targetLineColor: Colors.red),
                          charts.SparkLineDecoration(lineColor: Colors.black)
                        ],
                        backgroundDecorations: [
                          charts.GridDecoration(
                            showVerticalGrid: false,
                            verticalAxisStep: 1,
                            horizontalAxisStep: 1,
                            showHorizontalValues: false,
                            textStyle: const TextStyle(
                              fontSize: 24,
                            ),
                            gridColor: Theme.of(context).dividerColor,
                          )
                        ],
                      ),
                    ),

How can I avoid that? My wish/requirement is to move the line chart with the stock values down (there is a lot of fee space under the line chart) that the "TargetLineDecoration" will fit in the chart.

Best,
CC13

Specifying range of values on axis

Hello,

I have gotten a mostly satisfying result using the line-chart-example and customizing it. However, I would like to specify the range of values shown at the horizontal axis, meaning when I have data ranging from 17-27, I would only like to draw the chart in this range and not from 0-27. I have checked HorizontalAxisDecoration and GridDecoration for an option but can't find an option. I tried scaling the values down to 0-10 and then using horizontalAxisValueFromValue, to scale them back up to 17-27. Fells kinda hacky -- is this the intended way?

Also if that's the case, how do I make the chart show 27 as the highest value on the axis, if the highest value in the data is only 25, for example. Do I have to create a dummy-line with value 27 and show it with transparency 0 or something like that?

Thanks!

[Question] How to show reversed items and axisX values

I created a scrollable BarChart from right to left, like below.

final reversedData = logs.values.toList().reversed.map((e) => ChartItem<void>(e.length.toDouble())).toList();
// now reversed data is correct
final chartState = ChartState(
  data: ChartData.fromList(
    reversedData,
    axisMax: maxChartY.toDouble(),
  ),
...

// However it is wrong returned values in axis
VerticalAxisDecoration(
  // I try to reverse the index here
  valueFromIndex: (index) => "${logs.length - index}", 
  axisStep: 3
)

SingleChildScrollView(
  physics: const ScrollPhysics(),
  reverse: true,
  ...
}

How can I draw the xValues from the index starts from right to left like the chart item?

For the list with 10 items:
Currently: [0,3,6,9]
Expected: [9,6,3,0] => the 0 index is skipped from right.
Screen.Recording.2023-11-13.at.16.59.51.mov

WidgetItemOptions cannot have set item width

Setting the width of the Widget returned from widgetItemBuilder will not increase the width of the item.

The goal of the chart is to have a scrollable 7 item view with custom bar widgets, the inserted list of values is over 2000 items so the bars get squished so small they disappear.

how can i use my own bar widget implementation and set the amount of items to show in the chart?

APK will be increased by 28 MB when adding this package

Description

When adding this package, will this increase the size of the APK by 28 MB.

APK with this package: 45.5 MB
APK without the asset folder: 17 MB

Is the asset folder with all the GIFs really needed? I assume the GIFs and images in the asset are only for the README.md, right?

Too much horizontal padding

Let's say I have the following code.

import 'dart:math';

import 'package:charts_painter/chart.dart';
import 'package:flutter/material.dart';

void main() => runApp(const App());

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: TestChart(),
        ),
      ),
    );
  }
}

class TestChart extends StatelessWidget {
  const TestChart({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final data = [
      4000.0,
      4138.0,
      3000.0,
      2000.0,
      2700.0,
      3000.0,
      3185.0,
      1800.0
    ];
    final barValues = data.map((e) => BarValue<void>(e)).toList();
    final maxValue = data.reduce(max);
    final horizontalStepSize = max(2, (maxValue ~/ 8)).toDouble();

    return Chart(
      state: ChartState.bar(
        ChartData([barValues]),
        itemOptions: const BarItemOptions(
          padding: EdgeInsets.symmetric(horizontal: 2),
        ),
        backgroundDecorations: [
          GridDecoration(
            horizontalAxisStep: horizontalStepSize,
            showVerticalGrid: false,
            showVerticalValues: true,
            showHorizontalValues: true,
            showTopHorizontalValue: true,
            horizontalLegendPosition: HorizontalLegendPosition.start,
            textStyle: Theme.of(context).textTheme.bodyText2,
            gridColor: Colors.black12,
            verticalAxisValueFromIndex: (index) => index.toString(),
          ),
        ],
      ),
    );
  }
}
Version 1.1.0 Version 2.0.0
v1 v2

As you can see, in version 1.1.0, a couple of the horizontal values in the 2-thousands are trimmed. This has been fixed in version 2.0.0, but there's also a lot of unnecessary horizontal padding.

Chart are displayed incorrectly when axisMin and axisMax are set

First of all, thank you for this amazing library, I really like the idea of using widgets to create charts.

I'm creating a line chart with target line(y: 3.6) and target area(y: 0 ~ 2).
I encountered some issues after setting axisMin and axisMax:

WX20230910-125832@2x

1. I have a TargetAreaDecoration from 0 to 2, when axisMin is set to 2, it is incorrectly displayed on the x-axis.

The solution I came up with is to dynamically modify the targetMin and targetMax of the TargetAreaDecoration based on axisMin and axisMax. For example, when axisMin is 1, I would change the TargetAreaDecoration's targetMin from 0 to 1, so it won't be displayed on the x-axis anymore.

2. From the blue area in the graph, it can be observed that the rendering behavior of widgetItemBuilder is inconsistent with that of SparkLineDecoration. The size of widgetItemBuilder is much larger than SparkLineDecoration.

I created a Position Widget within widgetItemBuilder and positioned the point widget using its top property. If the verticalMultiplier parameter can be added to widgetItemBuilder, I can calculate the correct position of the point:

Position(
    bottom: (_mappedValues[data.listIndex][data.itemIndex].max! - axisMin) * verticalMultiplier + 8,
)

3. The target line is also being displayed in the wrong place.

The solution I came up with is similar to the first issue. I dynamically modify verticalMultiplier * (3.6 - axisMin) based on axisMin and axisMax.

Do you have any suggestions? Thank you.

Codes

import 'package:charts_painter/chart.dart';
import 'package:flutter/material.dart';

const double axisWidth = 80.0;

class LineChart extends StatelessWidget {
  final bool useAxis;

  LineChart({Key? key, this.useAxis = false}) : super(key: key);

  final List<List<ChartItem<double>>> _mappedValues = [
    [ChartItem(2.0), ChartItem(5.0), ChartItem(8.0), ChartItem(3.0), ChartItem(6.0)]
  ];

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: MediaQuery.of(context).size.height / 2,
      child: Row(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 32),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 350),
              width: axisWidth,
              child: DecorationsRenderer(
                [
                  HorizontalAxisDecoration(
                    asFixedDecoration: true,
                    lineWidth: 0,
                    axisStep: 2,
                    showValues: true,
                    endWithChart: false,
                    axisValue: (value) => '$value',
                    legendFontStyle: Theme.of(context).textTheme.bodyMedium,
                    valuesAlign: TextAlign.center,
                    valuesPadding: const EdgeInsets.only(left: -axisWidth, bottom: -10),
                    showLines: false,
                    showTopValue: true,
                  )
                ],
                ChartState<double>(
                  data: ChartData(
                    _mappedValues,
                    axisMin: useAxis ? 2 : null,
                    axisMax: useAxis ? 8 : null,
                    dataStrategy: const DefaultDataStrategy(stackMultipleValues: true),
                  ),
                  itemOptions: WidgetItemOptions(widgetItemBuilder: (data) {
                    return const SizedBox();
                  }),
                  backgroundDecorations: [
                    GridDecoration(
                      showVerticalValues: true,
                      verticalLegendPosition: VerticalLegendPosition.bottom,
                      verticalValuesPadding: const EdgeInsets.only(top: 8.0),
                      verticalAxisStep: 2,
                      gridWidth: 1,
                      textStyle: Theme.of(context).textTheme.labelSmall,
                    ),
                  ],
                ),
              ),
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 32),
              child: AnimatedChart<double>(
                width: MediaQuery.of(context).size.width - axisWidth - 8,
                duration: const Duration(milliseconds: 450),
                state: ChartState<double>(
                  data: ChartData(
                    _mappedValues,
                    axisMin: useAxis ? 2 : null,
                    axisMax: useAxis ? 8 : null,
                    dataStrategy: const DefaultDataStrategy(stackMultipleValues: true),
                  ),
                  itemOptions: WidgetItemOptions(widgetItemBuilder: (data) {
                    return Stack(
                      clipBehavior: Clip.none,
                      children: [
                        Container(color: Colors.blue.withOpacity(0.2)),
                        Positioned(
                          top: -24,
                          left: 0,
                          right: 0,
                          child: Column(
                            children: [
                              Center(
                                  child: Text(
                                      _mappedValues[data.listIndex][data.itemIndex].max.toString()))
                            ],
                          ),
                        ),
                        Positioned(
                          top: -5,
                          left: 0,
                          right: 0,
                          child: Column(
                            children: [
                              Center(
                                child: Container(
                                  width: 10,
                                  height: 10,
                                  decoration: BoxDecoration(
                                      color: Theme.of(context).colorScheme.primary,
                                      borderRadius: const BorderRadius.all(Radius.circular(8)),
                                      border: Border.all(
                                          width: 1.4,
                                          color: Theme.of(context).colorScheme.surface)),
                                ),
                              )
                            ],
                          ),
                        ),
                      ],
                    );
                  }),
                  foregroundDecorations: [],
                  backgroundDecorations: [
                    GridDecoration(
                      horizontalAxisStep: 2,
                      showVerticalGrid: false,
                      showVerticalValues: true,
                      verticalLegendPosition: VerticalLegendPosition.bottom,
                      verticalValuesPadding: const EdgeInsets.only(top: 8.0),
                      verticalAxisStep: 1,
                      gridColor: Theme.of(context).colorScheme.outline.withOpacity(0.3),
                      dashArray: [8, 8],
                      gridWidth: 1,
                      textStyle: Theme.of(context).textTheme.labelSmall,
                    ),
                    WidgetDecoration(
                      widgetDecorationBuilder:
                          (context, chartState, itemWidth, verticalMultiplier) {
                        return Padding(
                          padding: chartState.defaultMargin,
                          child: Stack(
                            children: [
                              Positioned(
                                right: 0,
                                left: 0,
                                bottom: verticalMultiplier * 3.6,
                                child: CustomPaint(painter: DashedLinePainter()),
                              ),
                            ],
                          ),
                        );
                      },
                    ),
                    TargetAreaDecoration(
                      targetAreaFillColor: Theme.of(context).colorScheme.error.withOpacity(0.6),
                      targetLineColor: Colors.transparent,
                      lineWidth: 0,
                      targetMax: 2,
                      targetMin: 0,
                    ),
                    SparkLineDecoration(
                      lineWidth: 2,
                      lineColor: Theme.of(context).colorScheme.primary,
                      smoothPoints: true,
                      listIndex: 0,
                    ),
                  ],
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class DashedLinePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    double dashWidth = 8, dashSpace = 8, startX = 0;
    final paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 1;
    while (startX < size.width) {
      canvas.drawLine(Offset(startX, 0), Offset(startX + dashWidth, 0), paint);
      startX += dashWidth + dashSpace;
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) => false;
}

Bar width is Bigger when we have few values

Hai thanks for the beautiful plugin,

I was using this plugin to show Bp Values over time,

when the data is less then my bar width is bigger , tried to fix this using provided keys
maxBarWidth, minBarWidth but still it doesn't helped me .

Screenshot 2023-08-20 at 1 18 51 AM

How can I have default bar width ? is any option for it . Please help out me here
Thanks in Advance.

Incorrect Offset calculation in TargetLineDecoration

When we setting ChartData. axisMin the Offset in TargetLineDecoration.applyPaintTransform calculates wrongly.

The problem is in:
Offset(state.defaultPadding.left + state.defaultMargin.left, _height - (state.defaultMargin.bottom + state.defaultPadding.bottom) - (scale * (target ?? 0.0) + _minValue);

instead of adding _minValue we should subtract it from scale * (target ?? 0.0)

Value Decoration - Candle Chart

Is it possible to display the top and bottom values on a candle chart?

I can display the bottom value with, but not sure if it's possible to add the top value of the range above each candle as well?
ValueDecoration(
alignment: Alignment.bottomCenter,
textStyle: Theme.of(context)
.textTheme
.button
.copyWith(color: Theme.of(context).colorScheme.onPrimary),
),

Take Swift Charts as inspiration

Apple introduced Swift Charts in WWDC22, a SwiftUI library to represent complex datasets using charts. It has a lot of similarities with this package, both in terms of functionality and looks.

The level of thought and detail put into Swift Charts is amazing, the API is clean, easy to use and orthogonal to help combine features quickly. The graphic aspect is also great, it has reasonable defaults, the right amount of customizability and renders beautifully almost always.

Now that this package is being further developed and with the upcoming breaking changes in the next major version (v3), I think it's a good opportunity to maybe “steal” some ideas from Swift Charts. If you don't find this proposal appealing for the next version of flutter-charts, at least it's good to know the state of the art and other “competing” implementations in the charting world.

Thanks for your work!

Further reference:

Scrollable chart demo not working

Hi I downloaded the package and Im playing around with your demo, but scrollable is not working correctly, Maybe Im not sure how it suppose to work? I try to drag and click and I looked for any type of scroll bar with no success I attach video of the issue, this is on WINDOWS.

Flutter 3.0.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f1875d570e (3 months ago) • 2022-07-13 11:24:16 -0700
Engine • revision e85ea0e79c
Tools • Dart 2.17.6 • DevTools 2.12.2

localhost_62587_._.-.Google.Chrome.2022-09-30.02-06-05.mp4

Error happen if I build for windows, either in debug or if I build it.

Migration issues

Hello, can you help me migrate this code to the new version.

  1. This way I could only see the current time bubble:
itemOptions: BubbleItemOptions(
    colorForValue: (_, value, [min]) {
        horaValor++;
        if (horaValor > 23) {
            horaValor = 0;
        }
        if (value != null) {                  
            if (hora == horaValor) {
                return Colors.white;
            }
        }
        return Colors.transparent;
    },
),

This doesn't work because it draws the bubble at the bottom:

itemOptions: BubbleItemOptions(
    maxBarWidth: 4,
    bubbleItemBuilder: (data) {
        if (data.itemIndex == hora) {
            return const BubbleItem(color: Colors.white);
        }
        return const BubbleItem(color: Colors.transparent);
    },
),
  1. This is how I managed to draw the middle line of the data:
backgroundDecorations: [
    TargetLineDecoration(
        target: dataHoy.calcularPrecioMedio(dataHoy.preciosHora),
        targetLineColor: Colors.blue,
        lineWidth: 1,
    ),
],

Thank you

Show labels on stacked Bar Chart in sections

Hello,

I want to show the values of a section in a stacked bar chart inside the section. Is there a way to do that? By using ValueDecoration I was only able to produce a label for the whole bar.

Tooltip Support.

It'll be very great if tooltips on tap feature is added especially in case on line charts with bubbles on data points.

Is there MaxVisibleRange option available in scrollable chart ?

Hi
I would like to ask a doubt related scrollable chart, is there available maxVisibleRange property in scrollable (Like if we have a view port, can we set a range it should be show in that range in any size of device also. I have checked with ipad and android mobile, there is candle items showing in graph view different numbers, in the sense in iPad there is more than 10 items showing in a view in scrollable chart, but in android pixel phone its showing only 7 items in a view(Actually I made a graph for Week view). so If have maxVisibleRange property it will help for my logic for loading more data, Its my question / doubt, can you please reply?
Thank you..

Add an image/images using Stack?

I'm relatively inexperienced, so not sure if this is possible - but I would like to add some images to the bottom of the candle chart I have created. I thought I would be able to use Stack and Positioned widgets to add them. However putting my chart widget into a Stack breaks the chart. Here are the images that show what happens when I add a Stack only. Is this possible to do?

Container(
      child:  rangeChart(context),
),

Screen Shot 2021-11-01 at 10 46 49 pm

Container(
         child: Stack(
                children: <Widget>[
                      rangeChart(context),
                 ]
         )
),

Screen Shot 2021-11-01 at 10 45 28 pm

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.